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

Need support for custom annotations #104

Closed
arubalac opened this issue Jun 12, 2015 · 7 comments

Comments

@arubalac
Copy link

commented Jun 12, 2015

Hello ,

Could you please guide us on the extension points (classes\resources) in the jaxrs-for-raml utility where we could extend and plug in support for custom annotations?

Currently most of our end-users have heavily used Custom Annotations to handle dynamic links for follow-up actions on actions performed. They would like to extend the utility to support their Custom Annotations.

Regards
Arun

@KonstantinSviridov

This comment has been minimized.

Copy link
Contributor

commented Jun 16, 2015

Hi, Arun

Right now we do not have extension points for custom annotations. Could you provide us some examples of such annotations and how they affect the resulting RAML? Once we've got them, we can start designing the extensions.

Regards, Konstantin.

@arubalac

This comment has been minimized.

Copy link
Author

commented Jun 19, 2015

Hi Konstantin,

Thanks for your response.
The custom annotations what we are looking here will reflect on the JSON example and schema. The below custom annotation will not have any direct impact on the RAML structure. Most of the custom annotations I have seen from my clients revolves around the JSON \ XML schema and examples.

The Below java class A use a custom annotation named @Relationship.List and @relationship.

Sample Java class A

package com.data.v1.output.vm;
import com.data.annotations.Relationship;
import com.rest.globals.Constants;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.annotate.JsonProperty;
@Relationship.List({
@relationship(key = "datacenters", template = "/{oid}/datacenters"),
@relationship(key = "hosts", template = "/{oid}/hosts"),
@relationship(key = "ndatacenters", template = "/{oid}/ndatacenters"),
@relationship(key = "nhosts", template = "/{oid}/nhosts")
})
public class VMOutput extends VOutput {
private String server;
@JsonProperty("infra_account")
private boolean infraAccount = true;
@JsonProperty("port_number")
private Integer portNumber;

private String username;

private String password;

@JsonProperty("sdk_url")
private String sdkUrl;

public VMOutput() {
    vType = Constants.VMWARE_CLOUD_STR;
}

public String getServer() {
    return server;
}

public void setServer(String server) {
    this.server = server;
}

public Integer getPortNumber() {
    return portNumber;
}

public void setPortNumber(Integer portNumber) {
    this.portNumber = portNumber;
}

public String getUsername() {
    return username;
}

public void setUsername(String username) {
    this.username = username;
}

@JsonIgnore
public String getPassword() {
    return password;
}
@JsonProperty
public void setPassword(String password) {
    this.password = password;
}
@JsonIgnore
public String getSdkUrl() {
    return sdkUrl;
}
@JsonProperty
public void setSdkUrl(String sdkUrl) {
    this.sdkUrl = sdkUrl;
}
public boolean isInfraAccount() {
    return infraAccount;
}
@JsonIgnore
public void setInfraAccount(boolean infraAccount) {
    this.infraAccount = infraAccount;
}
@Override
public boolean equals(Object o) {
    if(o instanceof VMOutput) {
        VMOutput account = (VMOutput)o;
        return (name != null && account.name != null && account.name.equals(name) &&
                username != null && account.username != null && account.username.equals(username) &&
                server != null && account.server != null && account.server.equals(server));
    }
    return false;
}

}

The above class A will generate the below JSON example.In the below JSON file you could could see that there is a relationships element section generated

{
"value": [
{
"object_type": "virtual_account",
"links": {
"self": "http://localhost:8080/test",
"capability": "http://localhost:8080/capabilities",
"metadata": "http://localhost:8080/metadata"
},
"properties": {
"oid": "",
"name": "v0000",
"description": "com-e-222",
"server": "",
"username": "admin",
"virtual_account_type": "v100",
"infra_account": true,
"port_number": 00,
"sdk_url": "/sdk"
},
"relationships": {
"datacenters": "http://localhost:8080/datacenters",
"hosts": "http://localhost:8080/hosts",
"ndatacenters": "http://localhost:8080/ndatacenters",
"nhosts": "http://localhost:8080/nhosts"
}
}
]
}

Similarly another customization is class B which demonstrates @CapbilityLink annotaiton.

package com.v1.output.vm;
import com.annotations.CapabilityLink;
import com.annotations.Link;
import com.v1.output.BaseOutput;
import com.globals.Constants;
import org.codehaus.jackson.annotate.JsonProperty;

@CapabilityLink(template="{virtualAccountType}")
@link(key = "metadata", template = "/{virtualAccountType}/metadata")
public class VirtualAccountOutput extends BaseOutput {

@JsonProperty("virtual_account_type")
protected String virtualAccountType;

public VirtualAccountOutput() {
    this.objectType = Constants.VIRTUAL_ACCOUNT;
}
public String getVirtualAccountType() {
    return virtualAccountType;
}

public void setVirtualAccountType(String virtualAccountType) {
    this.virtualAccountType = virtualAccountType;
}

}

The above class B will generate the below JSON example.In the below JSON file you could could see that there is a "capability" element section generated under "links" element

{ "value": [ { "object_type": "virtual_account", "links": { "self": "http://localhost:8080/test", "capability": "http://localhost:8080/vmware/capabilities", "metadata": "http://localhost:8080/vmware/metadata" }, "properties": { "oid": "aaaaaaaaaaaaaa", "name": "vc", "description": "com", "server": "0.0.0.0", "username": "admin", "virtual_account_type": "", "infra_account": true, "port_number": 43, "sdk_url": "/sdk" }
    }
]

}

@KonstantinSviridov

This comment has been minimized.

Copy link
Contributor

commented Jun 22, 2015

Hi, @arubalac

We think, the easiest solution would be to propose some interface wich allows to adjust schemes and examples, passed as strings:

import com.mulesoft.jaxrs.raml.annotation.model.ITypeModel;

public interface IPostProcessor{

    /**
     * @content Serialized example
     * @type Type the example is being generated for.
     * @mediaType Example media type: XML or JSON
     * @return Adjusted example, if the input example needs to be adjusted. Otherwise return null.
     **/
    String adjustExample(String content, ITypeModel type, String mediaType);

    /**
     * @content Serialized schema
     * @type Type the schema is being generated for.
     * @mediaType Schema media type: XML or JSON
     * @return Adjusted schema, if the input schema needs to be adjusted. Otherwise return null.
     **/
    String adjustSchema(String content, ITypeModel type, String mediaType);
}

This approach will prevent us from imidiately implementing extensibility for our generators, which is not easy at this point. Moreover, in case of XSD we use native JAXB generator and can not interfere in the process.

Does it sound fine?

Regards, Konstantin.

@KonstantinSviridov

This comment has been minimized.

Copy link
Contributor

commented Jun 22, 2015

One more thing.
The ITypeModel interface is a part of our java code model.
It contains information obout types fields (IFieldModel), methods (IMethodModel) and their annotations.

IFieldModel provides an ITypeModel instance for field type, and IMethodModel provides an ITypeModel instance for return type.

@KonstantinSviridov

This comment has been minimized.

Copy link
Contributor

commented Aug 17, 2015

Hi, @arubalac

We have implemented some support for custom annotations. It allows to modify schema model after it has been generated in default way. The result is achieved by implementing a Schema Model Builder Extension.

For your case we have:

import java.util.List;
import org.raml.schema.model.ISchemaProperty;
import org.raml.schema.model.ISchemaType;
import org.raml.schema.model.SimpleType;
import org.raml.schema.model.impl.PropertyModelImpl;
import org.raml.schema.model.impl.TypeModelImpl;

import com.mulesoft.jaxrs.raml.annotation.model.IAnnotationModel;
import com.mulesoft.jaxrs.raml.annotation.model.ITypeModel;
import com.mulesoft.jaxrs.raml.annotation.model.StructureType;
import com.mulesoft.jaxrs.raml.jaxb.ISchemaModelBuilderExtension;
import com.mulesoft.jaxrs.raml.jaxb.JAXBProperty;

public class RelationExtension implements ISchemaModelBuilderExtension{

    private static final String RELATIONSHIPS_LIST = "custom.annotations.Relationship.List";
    private static final String PROPERTY_NAME_RELATIONSHIPS = "relationships";

    public ISchemaProperty processProperty(JAXBProperty jaxbProp,
            ISchemaProperty prop) {
        return prop;
    }

    public void processType(ISchemaType type) {

        IAnnotationModel ann = type.getAnnotation(RELATIONSHIPS_LIST);
        if(ann==null){
            return;
        }
        IAnnotationModel[] subAnnotations = ann.getSubAnnotations("value");
        if (subAnnotations != null && subAnnotations.length > 0) {
            for (IAnnotationModel a : subAnnotations) {
                ISchemaProperty relProp = null;
                List<ISchemaProperty> properties = type.getProperties();
                if (properties != null && !properties.isEmpty()) {
                    for (ISchemaProperty p : properties) {
                        if (p.getName().equals(PROPERTY_NAME_RELATIONSHIPS)) {
                            relProp = p;
                            break;
                        }
                    }
                }
                if (relProp == null) {
                    relProp = new PropertyModelImpl(
                            PROPERTY_NAME_RELATIONSHIPS,
                            new TypeModelImpl("relation", "relation", null, StructureType.COMMON, null),
                            false,
                            false,
                            StructureType.COMMON,
                            null,
                            null);
                    type.addProperty(relProp);
                }
                ISchemaType relType = relProp.getType();

                String key = a.getValue("key");
                final String template = a.getValue("template");

                PropertyModelImpl relation =
                        new PropertyModelImpl(key,
                        SimpleType.STRING,
                        false,
                        false,
                        StructureType.COMMON,
                        null,
                        null) {

                    @Override
                    public String getDefaultValue() {
                        return template;
                    }
                };
                relType.addProperty(relation);
            }
        }
    }

    public void processModel(ISchemaType type) {

    }

    public boolean generateSchema(ITypeModel type) {

        IAnnotationModel ann = type.getAnnotationByCanonicalName(RELATIONSHIPS_LIST);
        return ann != null;
    }

}

If the extension class is located in a separate project (for example, jax-rs-to-raml-examples-aux), you enable it like:

<plugin>
                <version>1.3.4-SNAPSHOT</version>
                <groupId>org.raml.plugins</groupId>
                <artifactId>jaxrs-raml-maven-plugin</artifactId>
                <configuration>
                    <sourceDirectory>${basedir}/src/main/java</sourceDirectory>
                    <outputFile>${basedir}/target/generated-sources/jaxrs-raml/tests1/api.raml</outputFile>
                    <basePackageName>com.mulesoft.jaxrs.raml.annotation.tests</basePackageName>
                    <jaxrsVersion>2.0</jaxrsVersion>
                    <useJsr303Annotations>false</useJsr303Annotations>
                    <jsonMapper>jackson2</jsonMapper>
                    <removeOldOutput>true</removeOldOutput>
                    <extensions>
                        <extension>custom.extension.RelationExtension</extension>
                    </extensions>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>generate-raml</goal>
                        </goals>
                        <phase>test</phase>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>raml-for-jaxrs-examples</groupId>
                        <artifactId>jax-rs-to-raml-examples-aux</artifactId>
                        <version>0.0.1-SNAPSHOT</version>
                    </dependency>
                </dependencies>
            </plugin>

For the VMOutput class schema and example are as follows:

{
  "InfraAccount" : true ,
  "PortNumber" : 123 ,
  "Password" : "str1234" ,
  "SdkUrl" : "str1234" ,
  "Server" : "str1234" ,
  "Username" : "str1234" ,
  "relationships" : {
    "datacenters" : "/{oid}/datacenters" ,
    "hosts" : "/{oid}/hosts" ,
    "ndatacenters" : "/{oid}/ndatacenters" ,
    "nhosts" : "/{oid}/nhosts"
  }
}

and

{
  "$schema" : "http://json-schema.org/draft-03/schema" ,
  "type" : "object" ,
  "required" : true ,
  "properties" : {
    "InfraAccount" : {
      "required" : false ,
      "type" : "boolean"
    } ,
    "PortNumber" : {
      "required" : false ,
      "type" : "number"
    } ,
    "Password" : {
      "required" : false ,
      "type" : "string"
    } ,
    "SdkUrl" : {
      "required" : false ,
      "type" : "string"
    } ,
    "Server" : {
      "required" : false ,
      "type" : "string"
    } ,
    "Username" : {
      "required" : false ,
      "type" : "string"
    } ,
    "relationships" : {
      "required" : false ,
      "type" : "object" ,
      "properties" : {
        "datacenters" : {
          "required" : false ,
          "type" : "string"
        } ,
        "hosts" : {
          "required" : false ,
          "type" : "string"
        } ,
        "ndatacenters" : {
          "required" : false ,
          "type" : "string"
        } ,
        "nhosts" : {
          "required" : false ,
          "type" : "string"
        }
      }
    }
  }
}

The idea is to attach additional properties which correspond the @relations list.

Note that this instrument does not work for XSDs, as we use native JAXB generator for them.

Regards, Konstantin.

@fertroya

This comment has been minimized.

Copy link

commented Jun 26, 2017

Hi @KonstantinSviridov, is there a sample usage of custom annotations for JPA following the example above? I think it'd be a fairly common use case for this feature.

@jpbelang

This comment has been minimized.

Copy link
Contributor

commented Dec 23, 2017

2.0 supports custom annotations for the raml, but not for the schema...

@jpbelang jpbelang closed this Dec 23, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants
You can’t perform that action at this time.