mulesoft-labs / raml-for-jax-rs

This project is all about two way transformation of JAX-RS-annotated Java code to RAML API description and back.
Other
295 stars 181 forks source link

Swagger Annotations other than @APIResponse and @APIResponses are not supported #52

Closed vebhhav closed 9 years ago

vebhhav commented 9 years ago

-Swagger Annotations other than @APIResponse and @APIResponses are not supported; Example - @APIOperation;

petrochenko-pavel-a commented 9 years ago

Yep that's true it is clearly stated in project documentation actually. And yep there is a big space for improvement here (for example there are Spring annotations that are also nice to cover)

Regards, Pavel

vebhhav commented 9 years ago

Thanks. How can we help you to get this fixed?

Regards -veb

Sent from my iPhone

On Mar 16, 2015, at 2:43 PM, petrochenko-pavel-a notifications@github.com wrote:

Yep that's true it is clearly stated in project documentation actually. And yep there is a big space for improvement here (for example there are Spring annotations that are also nice to cover)

Regards, Pavel

— Reply to this email directly or view it on GitHub.

usarid commented 9 years ago

It's not a matter of a fix (it isn't broken), it's a matter of adding new functionality, right? Just making sure I understand it.

On Tue, Mar 17, 2015 at 10:40 AM, vebhhav notifications@github.com wrote:

Thanks. How can we help you to get this fixed?

Regards -veb

Sent from my iPhone

On Mar 16, 2015, at 2:43 PM, petrochenko-pavel-a < notifications@github.com> wrote:

Yep that's true it is clearly stated in project documentation actually. And yep there is a big space for improvement here (for example there are Spring annotations that are also nice to cover)

Regards, Pavel

— Reply to this email directly or view it on GitHub.

— Reply to this email directly or view it on GitHub https://github.com/mulesoft/raml-for-jax-rs/issues/52#issuecomment-82357309 .

petrochenko-pavel-a commented 9 years ago

Hi @vebhhav, I am going to work on adding this functionality into the project. Do you have any examples of use-cases that I may use for testing of this feature. (I already have couple in my mind, but it would be nice to be sure that we are on same plate). I am mostly interested in your vision of how it should behave in the cases when we have code annotated both with jax-rs and swagger annotations.

Thanks in advance, Pavel

arubalac commented 9 years ago

Hi @petrochenko-pavel-a, Connected to your response to Vebhav . Below is the behavior expectation on swagger annotation . @ApiResponses and @ApiResponse should be able to identify the model class mentioned in the annotation and generate the schema and sample instance (XML\JSON based on media type). @ApiResponses(value = { @ApiResponse(code=200, message="success", response=XYZ.class)

In case of JAX-RS when we give the request and response signature with JAX-B annotated model class it is generating the sample and schema instance which is good for requirement having less effort in making signature changes but for most of the case the best option we feel is to apply the request and response signature without making the change(update) on code which is by adding swagger annotation on existing code. Special Note: Observed that when using the JAX-B annotated model class , the annotation @xmlTransient is supposed to neglect the field during the schema or sample instance generation but could see that all the feilds irrepective of whether annotated with @xmltransient or @xmlelement is getting generated.

Thanks Arun

arubalac commented 9 years ago

Hi @petrochenko-pavel-a,

PFB the JAX-RS service and associated JAX-B class for your testing

JAX-RS class @Path("/employee") public class MacService extends AbstractRestServiceHandler {

protected UserService(MacBRestServiceProxy proxy, APIAccessController accessController) {
    super(proxy, accessController);
    responseBeanType = new TypeToken<ResponseBean<MacUser>>(){}.getType();
}

@GET
@Path("/{name}")
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@AllowAccess(roles = {APIRole.END_USER, APIRole.GROUPADMIN})
@ApiResponses(value = { @ApiResponse(code=200, message="success", response=MacUser.class),
        @ApiResponse(code=404, message="invalid user name")})

public Response getUser(@PathParam("name") String userId, @HeaderParam(API_KEY_HEADER_NAME)String restKey, @Context SecurityContext securityContext) {
    try {
        checkAccess(securityContext.getUserPrincipal().getName(), getRole(securityContext), userId);
    }
    catch (IllegalAccessException e) {
        logger.error(e.getMessage(), e);
        return Response.status(Response.Status.FORBIDDEN).entity(new ResponseBean<MacUser>(ApplicationErrorCodes.ACCESS_DENIED)).build();
    }
    return restServiceProxy.handleGet(InfraRequestMethod.GET_USER, userId, getResponseBeanType(), restKey, this);
}

@GET
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@ApiResponses(value = { @ApiResponse(code=200, message="success", response=MacUser.class)})
public Response listUsers(@HeaderParam(API_KEY_HEADER_NAME)String restKey) {
    return restServiceProxy.handleGetList(InfraRequestMethod.GET_ALL_USERS, null, getResponseBeanType(), restKey, this);
}

@POST
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@ApiResponses(value = { @ApiResponse(code=200, message="success", response=MacUser.class),
        @ApiResponse(code=400, message="User already exists")})
public Response addUser(MacUser clientUser, @HeaderParam(API_KEY_HEADER_NAME)String restKey) { 
    return restServiceProxy.handlePost(InfraRequestMethod.ADD_USER, clientUser, "name", restKey, this);
}

@DELETE
@Path("/{contractorname}")
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@ApiResponses(value = { @ApiResponse(code=200, message="success", response=MacDUser.class),
        @ApiResponse(code=400, message="failed to delete user"),
        @ApiResponse(code=409, message="user can not be deleted")})
public Response deleteUser(@PathParam("contractorname") String userId, @HeaderParam(API_KEY_HEADER_NAME)String restKey) {
    return restServiceProxy.handleDelete(InfraRequestMethod.DELETE_USER, userId, restKey, this);
}

@PUT
@Path("/{name}")
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@AllowAccess(roles = {APIRole.END_USER, APIRole.GROUPADMIN})
@ApiResponses(value = { @ApiResponse(code=200, message="success", response=MacDUser.class),
        @ApiResponse(code=400, message="fail to updated, invalid user")})
public Response modifyUser(MacUser userBeingModified, @HeaderParam(API_KEY_HEADER_NAME)String restKey,
        @Context SecurityContext securityContext) {
    try {
        checkAccess(securityContext.getUserPrincipal().getName(), getRole(securityContext), userBeingModified.getname());
    }
    catch (IllegalAccessException e) {
        logger.error(e.getMessage(), e);
        return Response.status(Response.Status.FORBIDDEN).entity(new ResponseBean<MacUser>(ApplicationErrorCodes.ACCESS_DENIED)).build();
    }
    return restServiceProxy.handlePut(InfraRequestMethod.UPDATE_USER, userBeingModified, restKey, this);
}

/**
 * Returns the instance of UserValidator
 *
 * @return
 * @throws Exception
 */
@Override
public IValidator<MacUser> getRestValidatorInstance() throws Exception {
    return UserValidator.class.newInstance();
}

} JAX-B model class @Relationships(values={"usergroup:/usergroups/{usergroup}"}) @XmlRootElement(name="users") @XmlType(name="usersType") @XmlAccessorType(XmlAccessType.FIELD) public class MacUser{ private static final long serialVersionUID = -4679643118148992782L;

private static final String ADMIN = "admin222";
private static final String INFRA_USER = "infraUser222";

@Id
@XmlElement
@NotNull
private String username;
@JsonProperty("first_name")
@XmlElement(name="first_name")
@Max(value=256, message="Max supported length is 256 characters")
private String firstName;
@JsonProperty("last_name")
@Max(value=256, message="Max supported length is 256 characters")
@XmlElement(name="last_name")
private String lastName;
@XmlElement
private String email;
@XmlElement
private String address;
//@JsonProperty("phone_number")
@XmlElement(name="phone_number")
private String phoneNumber;
@XmlTransient
private String password;
@XmlElement
private String role;
@XmlElement
private String usergroup;
//@JsonProperty("system_user")
@XmlElement(name="system_user")
private boolean systemUser;
//@JsonProperty("restricted_user")
@XmlElement(name="restricted_user")
private boolean restrictedUser;

// boolean fields to check what field is set from client
@XmlTransient
private boolean isUserNameSet;
@XmlTransient
private boolean isFirstNameSet;
@XmlTransient
private boolean isLastNameSet;
@XmlTransient
private boolean isEmailSet;
@XmlTransient
private boolean isAddressSet;
@XmlTransient
private boolean isPhoneNumberSet;
@XmlTransient
private boolean isPasswordSet;
@XmlTransient
private boolean isUserRoleSet;
@XmlTransient
private boolean isUserGroupSet;

public MacUser() {
    super();
}

public MacUser(String userId) {
    super();
    this.username = userId;
}

public String getUsername() {
    return username;
}

public void setUsername(String username) {
    this.username = username;
    setUserNameSet(Boolean.TRUE);
    if (StringUtils.isNotBlank(username)) {
        if (username.equals(ADMIN)) {
            setSystemUser(true);
            setRestrictedUser(false);
        } else if (username.equals(INFRA_USER)) {
            setSystemUser(true);
            setRestrictedUser(true);
        } else {
            setSystemUser(false);
            setRestrictedUser(false);
        }
    }
}

public String getUsergroup() {
    return usergroup;
}

public void setUsergroup(String userGrp) {
    this.usergroup = userGrp;
    setUserGroupSet(Boolean.TRUE);
}

public String getFirstName() {
    return firstName;
}

public void setFirstName(String firstName) {
    this.firstName = firstName;
    setFirstNameSet(Boolean.TRUE);
}

public String getLastName() {
    return lastName;
}

public void setLastName(String lastName) {
    this.lastName = lastName;
    setLastNameSet(Boolean.TRUE);
}

public String getEmail() {
    return email;
}

public void setEmail(String email) {
    this.email = email;
    setEmailSet(Boolean.TRUE);
}

public String getAddress() {
    return address;
}

public void setAddress(String address) {
    this.address = address;
    setAddressSet(Boolean.TRUE);
}

public String getPhoneNumber() {
    return phoneNumber;
}

public void setPhoneNumber(String phoneNumber) {
    this.phoneNumber = phoneNumber;
    setPhoneNumberSet(Boolean.TRUE);
}

//@JsonIgnore
public String getPassword() {
    return password;
}

//@JsonProperty
public void setPassword(String password) {
    this.password = password;
    setPasswordSet(Boolean.TRUE);
}

public String getRole() {
    return role;
}

public void setRole(String role) {
    this.role = role;
    setUserRoleSet(Boolean.TRUE);
}

/**
 * @return the isUserNameSet
 */
//@JsonIgnore
@XmlTransient
public boolean isUserNameSet() {
    return isUserNameSet;
}

/**
 * @param isUserNameSet
 *            the isUserNameSet to set
 */
@JsonProperty
public void setUserNameSet(boolean isUserNameSet) {
    this.isUserNameSet = isUserNameSet;
}

/**
 * @return the isFirstNameSet
 */
@JsonIgnore
public boolean isFirstNameSet() {
    return isFirstNameSet;
}

/**
 * @param isFirstNameSet
 *            the isFirstNameSet to set
 */
@JsonProperty
public void setFirstNameSet(boolean isFirstNameSet) {
    this.isFirstNameSet = isFirstNameSet;
}

/**
 * @return the isLastNameSet
 */
@JsonIgnore
@XmlTransient
public boolean isLastNameSet() {
    return isLastNameSet;
}

/**
 * @param isLastNameSet
 *            the isLastNameSet to set
 */
@JsonProperty
public void setLastNameSet(boolean isLastNameSet) {
    this.isLastNameSet = isLastNameSet;
}

/**
 * @return the isEmailSet
 */
@JsonIgnore
public boolean isEmailSet() {
    return isEmailSet;
}

/**
 * @param isEmailSet
 *            the isEmailSet to set
 */
@JsonProperty
public void setEmailSet(boolean isEmailSet) {
    this.isEmailSet = isEmailSet;
}

/**
 * @return the isAddressSet
 */
@JsonIgnore
public boolean isAddressSet() {
    return isAddressSet;
}

/**
 * @param isAddressSet
 *            the isAddressSet to set
 */
@JsonProperty
public void setAddressSet(boolean isAddressSet) {
    this.isAddressSet = isAddressSet;
}

/**
 * @return the isPhoneNumberSet
 */
@JsonIgnore
public boolean isPhoneNumberSet() {
    return isPhoneNumberSet;
}

/**
 * @param isPhoneNumberSet
 *            the isPhoneNumberSet to set
 */
@JsonProperty
public void setPhoneNumberSet(boolean isPhoneNumberSet) {
    this.isPhoneNumberSet = isPhoneNumberSet;
}

/**
 * @return the isPasswordSet
 */
@JsonIgnore
public boolean isPasswordSet() {
    return isPasswordSet;
}

/**
 * @param isPasswordSet
 *            the isPasswordSet to set
 */
@JsonProperty
public void setPasswordSet(boolean isPasswordSet) {
    this.isPasswordSet = isPasswordSet;
}

/**
 * @return the isUserRoleSet
 */
@JsonIgnore
public boolean isUserRoleSet() {
    return isUserRoleSet;
}

/**
 * @param isUserRoleSet
 *            the isUserRoleSet to set
 */
@JsonProperty
public void setUserRoleSet(boolean isUserRoleSet) {
    this.isUserRoleSet = isUserRoleSet;
}

/**
 * @return the isUserGroupSet
 */
@JsonIgnore
public boolean isUserGroupSet() {
    return isUserGroupSet;
}

/**
 * @param isUserGroupSet
 *            the isUserGroupSet to set
 */
@JsonProperty
public void setUserGroupSet(boolean isUserGroupSet) {
    this.isUserGroupSet = isUserGroupSet;
}

/**
 * @return the systemUser
 */
public boolean isSystemUser() {
    return systemUser;
}

/**
 * @param systemUser the systemUser to set
 */
public void setSystemUser(boolean systemUser) {
    this.systemUser = systemUser;
}

/**
 * @return the restrictedUser
 */
public boolean isRestrictedUser() {
    return restrictedUser;
}

/**
 * @param restrictedUser the restrictedUser to set
 */
public void setRestrictedUser(boolean restrictedUser) {
    this.restrictedUser = restrictedUser;
}

@Override
public String toString() {
    StringBuilder strb = new StringBuilder();
    strb.append(" User Name = " + this.getUsername());
    strb.append(" First Name = " + this.getFirstName());
    strb.append(" Last Name = " + this.getLastName());
    strb.append(" Email = " + this.getEmail());
    strb.append(" Address = " + this.getAddress());
    strb.append(" Phone number = " + this.getPhoneNumber());
    strb.append(" User role = " + this.getRole());
    strb.append(" User group = " + this.getUsergroup());
    return strb.toString();
}

@Override
public boolean equals(Object o) {
    if(o instanceof MacUser) {
        MacUser user = (MacUser)o;
        if(user.username.equals(username) &&
                user.usergroup.equals(usergroup) &&
                user.lastName.equals(lastName) &&
                user.firstName.equals(firstName) &&
                user.email.equals(email)) {
            return true;
        }
    }
    return false;

}

}

Thanks Arun

KonstantinSviridov commented 9 years ago

Fixed at 1.3.4-SNAPSHOT Now we have following annotations supported: @Api @ApiOperation @ApiParam @ApiResponse @ApiResponses

The rest of the swagger annotations do not seem to have clear mapping on RAML

arubalac commented 9 years ago

Thanks for the quick turn around on this fix. PFB the observation on the fix # 52

  1. Example is generated successfully for JSON media type.
  2. Schema is generated successfully for JSON media type.
  3. Example is generated successfully for XML media type.
  4. Schema not generated for XML media type.
  5. @XMLTransientElement is not hidden

Regards Arun

petrochenko-pavel-a commented 9 years ago
  1. in example?
  2. for which resourse?
arubalac commented 9 years ago

In the sample i have provided (above) , all the resources are having @Produce having XML as media type. eg : @GET @Path("/{name}") @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) @AllowAccess(roles = {APIRole.END_USER, APIRole.GROUPADMIN}) @ApiResponses(value = { @ApiResponse(code=200, message="success", response=MacUser.class), @ApiResponse(code=404, message="invalid user name")}) In RAML response 200 is generating the example section successfully but the schema section is only showing the reference name but there is no schema defined with that name under -Schemas seciton in RAML. It has only generated the schema refernce and definition for JSON type.

Regards Arun

petrochenko-pavel-a commented 9 years ago

Thanks for your info, digging into it. now.

Regards, Pavel

KonstantinSviridov commented 9 years ago

Hi, @arubalac

@XmlTransient is fixed Changes are deployed to 1.3.4-SNAPSOT

As for the XML schema, we do see it being generated. Does it still fail for you? If yes, please, provide more details as we can not reproduce the issue.

Regards, Konstantin

arubalac commented 9 years ago

Hi @petrochenko-pavel-a and @KonstantinSviridov

Thanks for the update . I have tried testing the updated code (1.3.4-SNAPSHOT) . Following are the observations:

  1. @XML transient is working fine.
  2. The XML schema is not getting generated as expected. Its only generating the schema reference name. PFA the RAML , model and service java class.
  3. Another bug that is popped was xml prolog is incorrect. PFA the RAML.This was working fine earlier, there was no issues.In the new update of code we found this issue.

I was using the eclipse plugin>/b> for generating the RAML . @vebhhav will provide the apiraml.zip file.

Regards Arun

vebhhav commented 9 years ago

Any updates Pavel?

Sent from my iPhone

On Mar 30, 2015, at 7:42 AM, petrochenko-pavel-a notifications@github.com wrote:

Thanks for your info, digging into it. now.

Regards, Pavel

— Reply to this email directly or view it on GitHub.

arubalac commented 9 years ago

Hi @vebhhav ,

Yet to get an update .

bikegriffith commented 9 years ago

This is looking really good. One problem I've noticed (in 1.3.3 as well), is that the @Api(description="foo") annotation on a resource generates the following in the RAML output

documentation: 
  - title: description
    content: !include docs/description.md

But docs/description.md is nowhere to be found. (I'd actually prefer to see it just generate a text description based on the javadoc, but I can't seem to get that to work either). How do I get descriptions?

petrochenko-pavel-a commented 9 years ago

Hi guys, sorry I am extremely busy on other project right now, I promise to look/fix everything over weekend.

Regards, Pavel

On Thu, Apr 9, 2015 at 8:06 PM, Mike Griffith notifications@github.com wrote:

This is looking really good. One problem I've noticed (in 1.3.3 as well), is that the @Api(description="foo") annotation on a resource generates the following in the RAML output

documentation:

  • title: description content: !include docs/description.md

But docs/description.md is nowhere to be found. (I'd actually prefer to see it just generate a text description based on the javadoc, but I can't seem to get that to work either). How do I get descriptions?

— Reply to this email directly or view it on GitHub https://github.com/mulesoft/raml-for-jax-rs/issues/52#issuecomment-91242733 .

vebhhav commented 9 years ago

Thanks Pavel..appreciate your effort in taking this to the end.

Regards -veb

Sent from my iPhone

On Apr 9, 2015, at 7:58 AM, petrochenko-pavel-a notifications@github.com wrote:

Hi guys, sorry I am extremely busy on other project right now, I promise to look/fix everything over weekend.

Regards, Pavel

On Thu, Apr 9, 2015 at 8:06 PM, Mike Griffith notifications@github.com wrote:

This is looking really good. One problem I've noticed (in 1.3.3 as well), is that the @Api(description="foo") annotation on a resource generates the following in the RAML output

documentation:

  • title: description content: !include docs/description.md

But docs/description.md is nowhere to be found. (I'd actually prefer to see it just generate a text description based on the javadoc, but I can't seem to get that to work either). How do I get descriptions?

— Reply to this email directly or view it on GitHub https://github.com/mulesoft/raml-for-jax-rs/issues/52#issuecomment-91242733 .

— Reply to this email directly or view it on GitHub.

arubalac commented 9 years ago

Thanks Pavel . Appreciate your effort.

dkirrane commented 9 years ago

There are a number of examples here: https://github.com/swagger-api/swagger-core/tree/develop_2.0/samples Swagger Annotations I'd like to see supported are @Api @ApiOperation @ApiParam @ApiResponses @ApiResponse @ApiModel and @ApiModelProperty

If the JAX-RS method took a Java POJO as parameter or returned it as response and it @Produces @Consumes "application/json" it would be great if the generated RAML had a schema and example autogenerated from this json POJO

petrochenko-pavel-a commented 9 years ago

Hm actually it already should work for a plain case when method just returns a POJO. (but I think you need something a bit different).

Actually we support @Api @ApiOperation @ApiParam @ApiResponses @ApiResponse already but it is not totally clear for me how you see support for @ApiModel and @ApiModelProperty so it will be great if you will be able to describe how you see it in a bit more details (especially @ApiModelProperty)

Thanks in advance, Pavel

KonstantinSviridov commented 9 years ago

@bikegriffith, descriptions are fixed

arubalac commented 9 years ago

Thanks a lot @petrochenko-pavel-a and @KonstantinSviridov, XML schema is getting generated as expected inline in RAML . XML pro-log issue also got resolved.

@vebhhav thanks for you support and coordination.

petrochenko-pavel-a commented 9 years ago

I am closing this issue for now, please let me know if something else needs to be improved.

Regards, Pavel

dirosden commented 9 years ago

Hi Pavel,

We ran into one more ask from the Engineering Team. Their current code uses Custom Annotation @Relationships(values={"usergroup:/usergroups/{usergroup}"}) to provide additional links in REST response. Need suggestions for handling Custom Annotation support by the team as an extension to the RAML-for-JAX-RS utility. Kindly suggest how to go about this request.

Thanks and Regards, Venkatesh