Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrong class in documentation generation #608

Closed
coding-bunny opened this issue Apr 23, 2018 · 17 comments
Closed

Wrong class in documentation generation #608

coding-bunny opened this issue Apr 23, 2018 · 17 comments

Comments

@coding-bunny
Copy link

Hello,

I might be missing something, but in one of API endpoints, I implemented a PATCH operation, which takes a DocumentType as parameter. This is however one of our custom models, and not the DocumentType defined in several namespaces.

The import statements etc are all correct, as is the annotations.
When running the maven build, my documentation get's generated, however the output contains the wrong DocumentType class. It's as if the plugin seems to take the first matching class, and not the one I specified.

@Postremus
Copy link
Contributor

Could you please add a small, failing example, so that we can reproduce your problem?

@coding-bunny
Copy link
Author

I can share some basic code on how we have it setup if that helps. But I'll have to do some digging in what dependencies etc we use everywhere.

@Postremus
Copy link
Contributor

Basic code example should be enough. Dependencies don't matter too much, please just make sure that you use the current version of swagger-maven-plugin (3.1.7).

@coding-bunny
Copy link
Author

we're on 3.1.6
I'll try updating and see if that resolves the problem actually.

@coding-bunny
Copy link
Author

Okay,

I've updated the plugin to 3.1.7, which took quite some effort due dependency conflicts I had to resolve on our side, but I succeeded. So now I've regenerated the swagger documentation on the 3.1.7.

This is the model that I have created:

public class DocumentType extends EntityModel {
	private boolean templateEnabled = false;
	private boolean ownersEnabled = false;
	private boolean uniqueIdEnabled = false;

	/**
	 * <p>
	 * Empty Constructor to support JAX-RS
	 * </p>
	 */
	public DocumentType() {

	}

	/**
	 * <p>
	 * Copy Constructor to support copying from the ICP model.
	 * </p>
	 *
	 * @param entity The {@link EntityModel} to copy from.
	 * @throws CMException Thrown when the copy fails
	 */
	public DocumentType(EntityModel entity) throws CMException {
		super(entity);

		boolean template = entity.getAttribute(CMItem.ATTRIBUTE_TEMPLATE_DETAILS) != null
				&& entity.getAttribute(CMItem.ATTRIBUTE_TEMPLATE_NAME) != null;
		boolean owners = entity.getChildEntities() != null && entity.getChildEntities().get(CMItem.ATTRIBUTE_MDS_OWNERS) != null;
		boolean uniqueId = entity.getAttributes().get(CMItem.ATTRIBUTE_MDS_ID) != null;

		setTemplateEnabled(template);
		setOwnersEnabled(owners);
		setUniqueIdEnabled(uniqueId);
	}

	/**
	 * <p>
	 * Sets whether templating is enabled for the Document Type or not.
	 * <b>This is for usage in the V1 API only</b>
	 * </p>
	 *
	 * @param enabled A boolean indicating whether templating is enabled or not.
	 */
	public void setTemplateEnabled(boolean enabled) {
		templateEnabled = enabled;
	}

	/**
	 * <p>
	 * Returns whether templating is enabled for the Document Type or not.
	 * <b>This is for usage in the V1 API only</b>
	 * </p>
	 *
	 * @return True if templating is enabled; otherwise false.
	 */
	@XmlElement(name = "templateEnabled")
	public boolean getTemplateEnabled() {
		return templateEnabled;
	}

	/**
	 * <p>
	 * Sets whether Ming.le Owners is enabled for the Document Type or not.
	 * <b>This is for usage in the V1 API only</b>
	 * </p>
	 *
	 * @param enabled A boolean indicating whether Owners is enabled or not.
	 */
	public void setOwnersEnabled(boolean enabled) {
		ownersEnabled = enabled;
	}

	/**
	 * <p>
	 * Returns whether Ming.le Owners is enabled for the Document Type or not.
	 * <b>This is for usage in the V1 API only</b>
	 * </p>
	 *
	 * @return True if Owners is enabled; otherwise false.
	 */
	@XmlElement(name = "ownersEnabled")
	public boolean getOwnersEnabled() {
		return ownersEnabled;
	}

	/**
	 * <p>
	 * Sets whether MDS_ID is enabled for the Document Type or not.
	 * <b>This is for usage in the V1 API only</b>
	 * </p>
	 *
	 * @param enabled A boolean indicating whether MDS_ID is enabled or not.
	 */
	public void setUniqueIdEnabled(boolean enabled) {
		uniqueIdEnabled = enabled;
	}

	/**
	 * <p>
	 * Returns whether MDS_ID is enabled for the Document Type or not.
	 * <b>This is for usage in the V1 API only</b>
	 * </p>
	 *
	 * @return True if MDS_ID is enabled; otherwise false.
	 */
	@XmlElement(name = "uniqueId")
	public boolean getUniqueIdEnabled() {
		return uniqueIdEnabled;
	}

	@Override
	public boolean equals(Object other) {
		if (other instanceof ValueSet) {
			return ((DocumentType) other).getName().equals(getName());
		} else {
			return false;
		}
	}

	@Override
	public int hashCode() {
		return getName().hashCode();
	}
}

The model inherits from a few classes that simply add more properties to it. There's really nothing special about this class, as it's just a data-container. In the upper classes a few properties are marked with @XmlElement to get the correct output generated etc.

The endpoint has been defined as follows:

@Produces(MediaType.APPLICATION_JSON)
@Path(RestUrls.BASE_URL_API_V1 + RestUrls.API_V1_DOCUMENT_TYPES_URL)
@Api(value = "DocumentTypes", hidden = true)
@Session(roles = { Role.ROLE_ADMINISTRATOR })
public class DocumentTypesResource {
	private static final GridLog LOGGER = GridLogger.getLogger(DocumentTypesResource.class);

	@Context private HttpServletRequest request;
@PATCH
	@ApiOperation(value = "Update an Entity", notes = "Updates the Entity inside IDM", response = DocumentType.class)
	@PublicApi
	@Path(RestUrls.API_V1_ENTITY_URL)
	@Session(roles = { Role.ROLE_ADMINISTRATOR })
	public Response update(@ApiParam(value = RestUrls.LANGUAGE_DESC, required = false) @QueryParam(RestUrls.LANGUAGE) String language,
			@ApiParam(value = "The name of the DocumentType to be updated.", required = true) @PathParam("name") String name,
			@ApiParam(value = "The JSON payload, representing the DocumentType.", required = true) DocumentType partialModel)
			throws CMException {
		RestHelper.logRequest(request, LOGGER);
		DocumentTypeService service = new DocumentTypeService(request, language);
		DocumentType modelToUpdate = service.read(name);

		service.update(modelToUpdate, partialModel);

		return CacheController.noCache(Response.ok().entity(service.read(name))).build();
	}
}

Basic Resource class that listen to incoming PATCH requests.
Everything work perfectly when using the endpoint, so that's not the problem.
However when I look at the generated Swagger documentation, the following class is generated instead:

DocumentType {
  name (string, optional),
  notations (NamedNodeMap, optional),
  internalSubset (string, optional),
  entities (NamedNodeMap, optional),
  systemId (string, optional),
  publicId (string, optional),
  attributes (NamedNodeMap, optional),
  baseURI (string, optional),
  lastChild (Node, optional),
  namespaceURI (string, optional),
  nodeValue (string, optional),
  ownerDocument (Document, optional),
  previousSibling (Node, optional),
  textContent (string, optional),
  prefix (string, optional),
  nodeName (string, optional),
  childNodes (NodeList, optional),
  firstChild (Node, optional),
  nodeType (integer, optional),
  nextSibling (Node, optional),
  localName (string, optional),
  parentNode (Node, optional)
}

Which is a completely different class than the one I have defined.
My guess is that the code is simply picking the first matching class based on name, completely ignoring the package.

Things I've tried:

  • specifying the fully qualified name
  • Using our custom annotation that overwrites the type

@Postremus
Copy link
Contributor

I tried to reproduce this behavior using a wildfly-swarm 2018.4.1 project, no luck.
However, I had to set hidden of the API annotation to false, so that any documentation was generated at all.
Could you please try that as well?
Would you please paste the generated swagger documentation as well?
Also, how is swagger-maven-plugin configured in your pom.xml?

Model package de.procrafting.tools.recordjarconverter.rest.model;

public class DocumentType {
private boolean templateEnabled = false;
private boolean ownersEnabled = false;
private boolean uniqueIdEnabled = false;

/**
 * <p>
 * Empty Constructor to support JAX-RS
 * </p>
 */
public DocumentType() {

}

public DocumentType(DocumentType entity) throws IllegalArgumentException {

    setTemplateEnabled(true);
    setOwnersEnabled(true);
    setUniqueIdEnabled(true);
}

/**
 * <p>
 * Sets whether templating is enabled for the Document Type or not.
 * <b>This is for usage in the V1 API only</b>
 * </p>
 *
 * @param enabled A boolean indicating whether templating is enabled or not.
 */
public void setTemplateEnabled(boolean enabled) {
    templateEnabled = enabled;
}

/**
 * <p>
 * Returns whether templating is enabled for the Document Type or not.
 * <b>This is for usage in the V1 API only</b>
 * </p>
 *
 * @return True if templating is enabled; otherwise false.
 */
public boolean getTemplateEnabled() {
    return templateEnabled;
}

/**
 * <p>
 * Sets whether Ming.le Owners is enabled for the Document Type or not.
 * <b>This is for usage in the V1 API only</b>
 * </p>
 *
 * @param enabled A boolean indicating whether Owners is enabled or not.
 */
public void setOwnersEnabled(boolean enabled) {
    ownersEnabled = enabled;
}

/**
 * <p>
 * Returns whether Ming.le Owners is enabled for the Document Type or not.
 * <b>This is for usage in the V1 API only</b>
 * </p>
 *
 * @return True if Owners is enabled; otherwise false.
 */
public boolean getOwnersEnabled() {
    return ownersEnabled;
}

/**
 * <p>
 * Sets whether MDS_ID is enabled for the Document Type or not.
 * <b>This is for usage in the V1 API only</b>
 * </p>
 *
 * @param enabled A boolean indicating whether MDS_ID is enabled or not.
 */
public void setUniqueIdEnabled(boolean enabled) {
    uniqueIdEnabled = enabled;
}

/**
 * <p>
 * Returns whether MDS_ID is enabled for the Document Type or not.
 * <b>This is for usage in the V1 API only</b>
 * </p>
 *
 * @return True if MDS_ID is enabled; otherwise false.
 */
public boolean getUniqueIdEnabled() {
    return uniqueIdEnabled;
}

}

Resource
package de.procrafting.tools.recordjarconverter.rest;

import de.procrafting.tools.recordjarconverter.PATCH;
import de.procrafting.tools.recordjarconverter.rest.model.DocumentType;
import de.procrafting.tools.recordjarconverter.rest.model.RecordJarFile;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Produces(MediaType.APPLICATION_JSON)
@Path("documents/")
@Api(value = "DocumentTypes", hidden = false)
public class RecordJarResource {

@Context
private HttpServletRequest request;

@PATCH
@ApiOperation(value = "Update an Entity", notes = "Updates the Entity inside IDM", response = DocumentType.class)
@Path("{name}")
public Response update(@ApiParam(value = "Some beautiful description for language tags.", required = false) @QueryParam("tag") String language,
                   @ApiParam(value = "The name of the DocumentType to be updated.", required = true) @PathParam("name") String name,
                   @ApiParam(value = "The JSON payload, representing the DocumentType.", required = true) DocumentType partialModel)
    throws IllegalArgumentException {
return Response.ok().build();
}
}

@coding-bunny
Copy link
Author

We have a customized parser that overwrites the hidden flag depending on the kind of build we are doing. The issue not that no documentation is generated, it's the wrong documentation.

What do you mean with paste the generated swagger documentation?
Do you want to json file?

The plugin is configured as follows in our pom.xml:

<plugin>
        <groupId>com.github.kongchen</groupId>
        <artifactId>swagger-maven-plugin</artifactId>
        <executions>
          <execution>
            <id>generate-public</id>
            <phase>prepare-package</phase>
            <goals>
              <goal>generate</goal>
            </goals>
            <configuration>
              <apiSources>
                <apiSource>
                  <info>
                    <title>IDM REST API</title>
                    <version>&lt;jsp version/></version>
                  </info>
                  <locations>
                    <location>com.infor.documentarchive.rest</location>
                  </locations>
                  <basePath>/&lt;jsp caContextRoot/>/api</basePath>
                  <swaggerDirectory>${project.basedir}/src/main/resources/web/apidoc/rest/json</swaggerDirectory>
                  <swaggerApiReader>com.infor.documentarchive.commons.rest.SwaggerApiReader</swaggerApiReader>
                  <modelConverters>
                    <modelConverter>com.infor.documentarchive.commons.rest.SwaggerModelConverter</modelConverter>
                  </modelConverters>
                </apiSource>
              </apiSources>
            </configuration>
          </execution>
          <execution>
            <id>generate-internal</id>
            <phase>prepare-package</phase>
            <goals>
              <goal>generate</goal>
            </goals>
            <configuration>
              <apiSources>
                <apiSource>
                  <info>
                    <title>IDM REST API</title>
                    <version>&lt;jsp version/></version>
                  </info>
                  <locations>
                    <location>com.infor.documentarchive.rest</location>
                  </locations>
                  <basePath>/&lt;jsp caContextRoot/>/api</basePath>
                  <swaggerDirectory>${project.basedir}/src/main/resources/web/apidoc/rest/json</swaggerDirectory>
                  <swaggerFileName>full_swagger</swaggerFileName>
                  <swaggerApiReader>com.infor.documentarchive.commons.rest.SwaggerExtendedApiReader</swaggerApiReader>
                  <modelConverters>
                    <modelConverter>com.infor.documentarchive.commons.rest.SwaggerModelConverter</modelConverter>
                  </modelConverters>
                </apiSource>
              </apiSources>
            </configuration>
          </execution>
        </executions>
      </plugin>

We have 2 executions, generating 2 different JSON files.
One being the normal documentation we give to users, one for internal usage for developers.
Our application returns the correct JSON depending on how the system is configured.
They get generated as expected.

@Postremus
Copy link
Contributor

Yes, the geneated swagger.json file would be helpful.
Would you please try setting springmvc to false in your configuration? It defaults to true. IIrc swagger-maven-plugin uses a somewhat different codepath based on this setting.
Is SwaggerExtendedApiReader a complete new implementation of AbstractReader, or is it a subclass of an already existing reader in swagger-maven-plugin? If so, which existing reader got extended here?

My current best guess is that your custom reader and the swagger-maven-plugin don't play along nicely, since this is the only big difference in our configurations.

@coding-bunny
Copy link
Author

The swagger.json is rather large, will try to upload it somewhere and give you the link.
In the meanwhile I'll try to suggest the change you mentioned and see if that produces anything different.

The readers inherit from the JaxrsReader where we overwrite a few functions to support our annotations, but even with them removed the result remains the same.

Will be on three week vacation, but will keep you posted

@coding-bunny
Copy link
Author

Tried setting the springmvc configuration to false, did not seem to have any direct result/difference on the generated documentation.

Here's a link to the gist for the fully generated swagger file: https://gist.github.com/coding-bunny/56a586d425846faaea74817dca398fe5

@Postremus
Copy link
Contributor

I looked at the generated swagger.json, but did not see anything unusual.
This leaves, at least in my opinion, the com.infor.documentarchive.commons.rest.SwaggerModelConverter as only possible place for this error.
What happens if you try to generate your swagger doc without this ModelConverter, if possible?

@coding-bunny
Copy link
Author

running a test, hold on

@coding-bunny
Copy link
Author

not getting much difference unfortunately.

  1. I've removed the ModelConverter as requested, so the pom.xml was reduced in configuration.
  2. Updated my ApiReader to be even more simple, to not convert/change anything.
public class SwaggerExtendedApiReader extends JaxrsReader {

	public SwaggerExtendedApiReader(Swagger swagger, Log log) {
		super(swagger, log);
	}

	/**
	 * <p>
	 * Override the public read method, and enforce the reading of hidden operations.
	 * </p>
	 */
	@Override
	public Swagger read(Class<?> cls) {
		return read(cls, "", null, true, new String[0], new String[0], new HashMap<String, Tag>(), new ArrayList<Parameter>());
	}
}

So it simply exposes hidden APIs when it's used, otherwise relies on the standard implementation.
Swagger still seems to be using some kind of different class for the example unfortunately:

image

I have no idea to what this is being mapped, but it doesn't resemble a single class we have in use for our code.

@Postremus
Copy link
Contributor

Postremus commented May 2, 2018

Can you please try extending your SwaggerExtendedApiReader from com.github.kongchen.swagger.docgen.reader.SwaggerReader. This reader only calls the io.swagger.jaxrs.Reader from Swagger internally, and does nothing more.
This way, we can figure out if the problem lies somewhere in the swagger-core itself or in swagger-maven-plugin, and can therefore restrict where we have to search for this bug.

@coding-bunny
Copy link
Author

coding-bunny commented May 2, 2018 via email

@coding-bunny
Copy link
Author

I've changed the inheritance to the class mentioned, however it still does not resolve the 2 problems:

  • Wrong class generated for my DocumentType entity
  • Jackson annotations ignored in Schema generation

This is my extended reader to make sure all hidden api endpoints are read:

public class SwaggerExtendedApiReader extends SwaggerApiReader {

	public SwaggerExtendedApiReader(Swagger swagger, Log log) {
		super(swagger, log);
	}

	@Override
	protected boolean canReadApi(boolean readHidden, Api api) {
		return super.canReadApi(true, api);
	}
}

This is our class that does some tweaking to certain class parsing to ensure our customer SDK values are supported:

public class SwaggerApiReader extends com.github.kongchen.swagger.docgen.reader.SwaggerReader {
	public static final String API_PATH = "/" + RestUrls.API_NAME;

	private boolean normalizePath = false;

	public SwaggerApiReader(Swagger swagger, Log log) {
		super(swagger, log);

		if (swagger.getBasePath() != null && swagger.getBasePath().endsWith(API_PATH)) {
			normalizePath = true;
		}
	}

	/*
	 * (non-Javadoc)
	 * @see com.github.kongchen.swagger.docgen.reader.AbstractReader#parseOperationPath(java.lang.String, java.util.Map)
	 */
	@Override
	protected String parseOperationPath(String operationPath, Map<String, String> regexMap) {
		// Remove "/api" from the beginning of the path so we can get this to work with ION Api
		if (normalizePath && operationPath != null && operationPath.startsWith(API_PATH)) {
			operationPath = operationPath.substring(API_PATH.length());
		}

		return super.parseOperationPath(operationPath, regexMap);
	}

	/*
	 * We override this method instead of doing from SwaggerModelConverter since we don't have access to Annotations in that class,
	 * it is always null for some strange reason.
	 * (non-Javadoc)
	 * @see com.github.kongchen.swagger.docgen.reader.AbstractReader#getParameters(java.lang.reflect.Type, java.util.List)
	 */
	@Override
	protected List<Parameter> getParameters(Type type, List<Annotation> annotations) {
		JavaType _type = Json.mapper().constructType(type);
		if (_type != null && (SerializableDocument.class.isAssignableFrom(_type.getRawClass())
				|| String.class.isAssignableFrom(_type.getRawClass()))) {
			// If an output class has been specfied then use that otherwise fallback to String
			SwaggerDataType poc = JsonSerializer.getAnnotation(SwaggerDataType.class, annotations.toArray(new Annotation[0]));
			if (poc != null) {
				type = poc.type();
			} else {
				type = String.class;
			}
		}

		return super.getParameters(type, annotations);
	}
}

@coding-bunny
Copy link
Author

I've finally solved the problem!

For some reason our Swagger configuration was not loading the Jackson annotation module, ignoring all annotations used.
By extending our custom class with the following line in the constructor it all started working:

public SwaggerApiReader(Swagger swagger, Log log) {
		super(swagger, log);

		Json.mapper().registerModule(new com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule()); // Fox jackson annotation support

		if (swagger.getBasePath() != null && swagger.getBasePath().endsWith(API_PATH)) {
			normalizePath = true;
		}
	}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants