Java tutorial
/** * Copyright (C) 2014 the original author or authors. * See the notice.md file distributed with this work for additional * information regarding copyright ownership. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.ctriposs.rest4j.internal.server.model; import com.ctriposs.rest4j.internal.server.Rest4JInternalException; import com.ctriposs.data.DataMap; import com.ctriposs.data.codec.DataCodec; import com.ctriposs.data.codec.JacksonDataCodec; import com.ctriposs.data.schema.DataSchema; import com.ctriposs.data.schema.JsonBuilder; import com.ctriposs.data.schema.NamedDataSchema; import com.ctriposs.data.schema.PrimitiveDataSchema; import com.ctriposs.data.schema.SchemaToJsonEncoder; import com.ctriposs.data.schema.TyperefDataSchema; import com.ctriposs.data.schema.UnionDataSchema; import com.ctriposs.data.template.DataTemplate; import com.ctriposs.data.template.DataTemplateUtil; import com.ctriposs.data.template.HasTyperefInfo; import com.ctriposs.data.template.StringArray; import com.ctriposs.data.template.TyperefInfo; import com.ctriposs.rest4j.common.ActionResponse; import com.ctriposs.rest4j.common.ComplexResourceKey; import com.ctriposs.rest4j.common.ResourceMethod; import com.ctriposs.rest4j.common.RestConstants; import com.ctriposs.rest4j.restspec.ActionSchema; import com.ctriposs.rest4j.restspec.ActionSchemaArray; import com.ctriposs.rest4j.restspec.ActionsSetSchema; import com.ctriposs.rest4j.restspec.AssocKeySchema; import com.ctriposs.rest4j.restspec.AssocKeySchemaArray; import com.ctriposs.rest4j.restspec.AssociationSchema; import com.ctriposs.rest4j.restspec.CollectionSchema; import com.ctriposs.rest4j.restspec.CustomAnnotationContentSchemaMap; import com.ctriposs.rest4j.restspec.EntitySchema; import com.ctriposs.rest4j.restspec.FinderSchema; import com.ctriposs.rest4j.restspec.FinderSchemaArray; import com.ctriposs.rest4j.restspec.IdentifierSchema; import com.ctriposs.rest4j.restspec.MetadataSchema; import com.ctriposs.rest4j.restspec.ParameterSchema; import com.ctriposs.rest4j.restspec.ParameterSchemaArray; import com.ctriposs.rest4j.restspec.ResourceSchema; import com.ctriposs.rest4j.restspec.ResourceSchemaArray; import com.ctriposs.rest4j.restspec.RestMethodSchema; import com.ctriposs.rest4j.restspec.RestMethodSchemaArray; import com.ctriposs.rest4j.restspec.SimpleSchema; import com.ctriposs.rest4j.server.Key; import com.ctriposs.rest4j.server.ResourceLevel; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Set; import org.apache.commons.io.IOUtils; /** * Encodes a ResourceModel (runtime-reflection oriented class) into the JSON-serializable * {@link ResourceSchema}. Accepts a {@link DocsProvider} plugin to incorporate documentation * from a JVM language such as JavaDoc and Scaladoc. * */ public class ResourceModelEncoder { public static final String DEPRECATED_ANNOTATION_NAME = "deprecated"; public static final String DEPRECATED_ANNOTATION_DOC_FIELD = "doc"; public static final String COMPOUND_KEY_TYPE_NAME = "CompoundKey"; private final DataCodec codec = new JacksonDataCodec(); /** * Provides documentation strings from a JVM language to be incorporated into ResourceModels. */ public interface DocsProvider { /** * Gets the file extensions this doc provider supports. It must ignore any source files registered with it that * are not in this set. * * @return the supported files extensions */ Set<String> supportedFileExtensions(); /** * Registers the source files with the doc provider. The doc provider should perform any initialization required * handle subsequent requests for classe, method and param docs from these source files. * * @param filenames provides the source file names to register. */ void registerSourceFiles(Collection<String> filenames); /** * @param resourceClass resource class * @return class documentation, or null if no documentation is available */ String getClassDoc(Class<?> resourceClass); String getClassDeprecatedTag(Class<?> resourceClass); /** * @param method resource {@link Method} * @return method documentation, or null if no documentation is available */ String getMethodDoc(Method method); String getMethodDeprecatedTag(Method method); /** * @param method resource {@link Method} * @param name method param name * @return method param documentation, or null if no documentation is available */ String getParamDoc(Method method, String name); } public static class NullDocsProvider implements DocsProvider { @Override public void registerSourceFiles(Collection<String> filenames) { } @Override public Set<String> supportedFileExtensions() { return Collections.emptySet(); } @Override public String getClassDoc(final Class<?> resourceClass) { return null; } @Override public String getClassDeprecatedTag(Class<?> resourceClass) { return null; } @Override public String getMethodDoc(final Method method) { return null; } @Override public String getMethodDeprecatedTag(Method method) { return null; } @Override public String getParamDoc(final Method method, final String name) { return null; } } private final DocsProvider _docsProvider; /** * @param docsProvider {@link DocsProvider} to pull in javadoc comments. */ public ResourceModelEncoder(final DocsProvider docsProvider) { _docsProvider = docsProvider; } /** * @param resourceModel {@link ResourceModel} to build the schema for * @return {@link ResourceSchema} for the provided resource model */ public ResourceSchema buildResourceSchema(final ResourceModel resourceModel) { ResourceSchema rootNode = new ResourceSchema(); switch (resourceModel.getResourceType()) { case ACTIONS: appendActionsModel(rootNode, resourceModel); break; case SIMPLE: appendSimple(rootNode, resourceModel); break; default: appendCollection(rootNode, resourceModel); break; } final DataMap customAnnotation = resourceModel.getCustomAnnotationData(); if (!customAnnotation.isEmpty()) { rootNode.setAnnotations(new CustomAnnotationContentSchemaMap(customAnnotation)); } return rootNode; } /** * Checks if a matching .restspec.json file exists in the classpath for the given {@link ResourceModel}. * If one is found it is loaded. If not, one is built from the {@link ResourceModel}. * * The .restspec.json is preferred because it contains the exact idl that was generated for the resource * and also includees includes javadoc from the server class in the restspec.json. * * @param resourceModel provides the name and namespace of the schema to load or build * @return the {@link ResourceSchema} for the given {@link ResourceModel} */ public ResourceSchema loadOrBuildResourceSchema(final ResourceModel resourceModel) { StringBuilder resourceFilePath = new StringBuilder(File.separator); if (resourceModel.getNamespace() != null) { resourceFilePath.append(resourceModel.getNamespace()); resourceFilePath.append("."); } resourceFilePath.append(resourceModel.getName()); resourceFilePath.append(RestConstants.RESOURCE_MODEL_FILENAME_EXTENSION); try { InputStream stream = this.getClass().getClassLoader().getResourceAsStream(resourceFilePath.toString()); if (stream == null) { // restspec.json file not found, building one instead return buildResourceSchema(resourceModel); } else { DataMap resourceSchemaDataMap = codec.bytesToMap(IOUtils.toByteArray(stream)); return new ResourceSchema(resourceSchemaDataMap); } } catch (IOException e) { throw new RuntimeException("Failed to read " + resourceFilePath.toString() + " from classpath.", e); } } /*package*/ static String buildDataSchemaType(final Class<?> type) { final DataSchema schema = DataTemplateUtil.getSchema(type); return buildDataSchemaType(schema); } /*package*/ static String buildDataSchemaType(DataSchema schema) { if (schema instanceof PrimitiveDataSchema || schema instanceof NamedDataSchema) { return schema.getUnionMemberKey(); } JsonBuilder builder = null; try { builder = new JsonBuilder(JsonBuilder.Pretty.SPACES); final SchemaToJsonEncoder encoder = new NamedSchemaReferencingJsonEncoder(builder); encoder.encode(schema); return builder.result(); } catch (IOException e) { throw new Rest4JInternalException("could not encode schema for '" + schema.toString() + "'", e); } finally { if (builder != null) { builder.closeQuietly(); } } } /** * SchemaToJsonEncoder which encodes all NamedDataSchemas as name references. This encoder * never inlines the full schema text of a NamedDataSchema. */ private static class NamedSchemaReferencingJsonEncoder extends SchemaToJsonEncoder { public NamedSchemaReferencingJsonEncoder(final JsonBuilder builder) { super(builder); } @Override protected void encodeNamed(final NamedDataSchema schema) throws IOException { writeSchemaName(schema); return; } } private static String buildDataSchemaType(final Class<?> type, final DataSchema dataSchema) { final DataSchema schemaToEncode; if (dataSchema instanceof TyperefDataSchema) { return ((TyperefDataSchema) dataSchema).getFullName(); } else if (dataSchema instanceof PrimitiveDataSchema || dataSchema instanceof NamedDataSchema) { return dataSchema.getUnionMemberKey(); } else if (dataSchema instanceof UnionDataSchema && HasTyperefInfo.class.isAssignableFrom(type)) { final TyperefInfo unionRef = DataTemplateUtil.getTyperefInfo(type.asSubclass(DataTemplate.class)); schemaToEncode = unionRef.getSchema(); } else { schemaToEncode = dataSchema; } JsonBuilder builder = null; try { builder = new JsonBuilder(JsonBuilder.Pretty.SPACES); final SchemaToJsonEncoder encoder = new NamedSchemaReferencingJsonEncoder(builder); encoder.encode(schemaToEncode); return builder.result(); } catch (IOException e) { throw new Rest4JInternalException("could not encode schema for '" + type.getName() + "'", e); } finally { if (builder != null) { builder.closeQuietly(); } } } public static String buildPath(final ResourceModel resourceModel) { StringBuilder sb = new StringBuilder(); buildPathInternal(resourceModel, sb, false); return sb.toString(); } private static String buildPathForEntity(final ResourceModel resourceModel) { StringBuilder sb = new StringBuilder(); buildPathInternal(resourceModel, sb, true); return sb.toString(); } private static void buildPathInternal(ResourceModel resourceModel, final StringBuilder sb, boolean addEntityElement) { do { if (addEntityElement) { if (resourceModel.getKeys().size() >= 1) { sb.insert(0, "/{" + resourceModel.getKeyName() + "}"); } } sb.insert(0, "/" + resourceModel.getName()); addEntityElement = true; } while ((resourceModel = resourceModel.getParentResourceModel()) != null); } private void appendCommon(final ResourceModel resourceModel, final ResourceSchema resourceSchema) { resourceSchema.setName(resourceModel.getName()); if (!resourceModel.getNamespace().isEmpty()) { resourceSchema.setNamespace(resourceModel.getNamespace()); } resourceSchema.setPath(buildPath(resourceModel)); final Class<?> valueClass = resourceModel.getValueClass(); if (valueClass != null) { resourceSchema.setSchema(DataTemplateUtil.getSchema(valueClass).getUnionMemberKey()); } final Class<?> resourceClass = resourceModel.getResourceClass(); final String doc = _docsProvider.getClassDoc(resourceClass); final StringBuilder docBuilder = new StringBuilder(); if (doc != null) { docBuilder.append(doc).append("\n\n"); } docBuilder.append("generated from: ").append(resourceClass.getCanonicalName()); final String deprecatedDoc = _docsProvider.getClassDeprecatedTag(resourceClass); if (deprecatedDoc != null) { DataMap customAnnotationData = resourceModel.getCustomAnnotationData(); if (customAnnotationData == null) { customAnnotationData = new DataMap(); resourceModel.setCustomAnnotation(customAnnotationData); } customAnnotationData.put(DEPRECATED_ANNOTATION_NAME, deprecateDocToAnnotationMap(deprecatedDoc)); } resourceSchema.setDoc(docBuilder.toString()); } private void appendCollection(final ResourceSchema resourceSchema, final ResourceModel collectionModel) { appendCommon(collectionModel, resourceSchema); CollectionSchema collectionSchema = new CollectionSchema(); //HACK: AssociationSchema and CollectionSchema share many common elements, but have no inheritance //relationship. Here, we construct them both as facades on the same DataMap, which allows //us to pass strongly typed CollectionSchema objects around, even when we're dealing with //an association. AssociationSchema associationSchema = new AssociationSchema(collectionSchema.data()); if (collectionModel.getKeys().size() == 1) { appendIdentifierNode(collectionSchema, collectionModel); } else { appendKeys(associationSchema, collectionModel); } appendSupportsNodeToCollectionSchema(collectionSchema, collectionModel); appendMethodsToCollectionSchema(collectionSchema, collectionModel); FinderSchemaArray finders = createFinders(collectionModel); if (finders.size() > 0) { collectionSchema.setFinders(finders); } ActionSchemaArray actions = createActions(collectionModel, ResourceLevel.COLLECTION); if (actions.size() > 0) { collectionSchema.setActions(actions); } appendEntityToCollectionSchema(collectionSchema, collectionModel); switch (collectionModel.getResourceType()) { case COLLECTION: resourceSchema.setCollection(collectionSchema); break; case ASSOCIATION: resourceSchema.setAssociation(associationSchema); break; default: throw new IllegalArgumentException("unsupported resource type"); } } private void appendActionsModel(final ResourceSchema resourceSchema, final ResourceModel resourceModel) { appendCommon(resourceModel, resourceSchema); ActionsSetSchema actionsNode = new ActionsSetSchema(); ActionSchemaArray actions = createActions(resourceModel, ResourceLevel.COLLECTION); if (actions.size() > 0) { actionsNode.setActions(actions); } resourceSchema.setActionsSet(actionsNode); } private void appendSimple(ResourceSchema resourceSchema, ResourceModel resourceModel) { appendCommon(resourceModel, resourceSchema); SimpleSchema simpleSchema = new SimpleSchema(); appendSupportsNodeToSimpleSchema(simpleSchema, resourceModel); appendMethodsToSimpleSchema(simpleSchema, resourceModel); ActionSchemaArray actions = createActions(resourceModel, ResourceLevel.ENTITY); if (actions.size() > 0) { simpleSchema.setActions(actions); } appendEntityToSimpleSchema(simpleSchema, resourceModel); resourceSchema.setSimple(simpleSchema); } private void appendEntityToCollectionSchema(final CollectionSchema collectionSchema, final ResourceModel resourceModel) { EntitySchema entityNode = buildEntitySchema(resourceModel); collectionSchema.setEntity(entityNode); } private void appendEntityToSimpleSchema(final SimpleSchema simpleSchema, final ResourceModel resourceModel) { EntitySchema entityNode = buildEntitySchema(resourceModel); simpleSchema.setEntity(entityNode); } private EntitySchema buildEntitySchema(ResourceModel resourceModel) { EntitySchema entityNode = new EntitySchema(); entityNode.setPath(buildPathForEntity(resourceModel)); if (resourceModel.getResourceLevel() == ResourceLevel.COLLECTION) { ActionSchemaArray actions = createActions(resourceModel, ResourceLevel.ENTITY); if (actions.size() > 0) { entityNode.setActions(actions); } } // subresources ResourceSchemaArray subresources = new ResourceSchemaArray(); for (ResourceModel subResourceModel : resourceModel.getSubResources()) { ResourceSchema subresource = new ResourceSchema(); switch (subResourceModel.getResourceType()) { case COLLECTION: case ASSOCIATION: appendCollection(subresource, subResourceModel); break; case SIMPLE: appendSimple(subresource, subResourceModel); break; default: break; } final DataMap customAnnotation = subResourceModel.getCustomAnnotationData(); if (!customAnnotation.isEmpty()) { subresource.setAnnotations(new CustomAnnotationContentSchemaMap(customAnnotation)); } subresources.add(subresource); } if (subresources.size() > 0) { Collections.sort(subresources, new Comparator<ResourceSchema>() { @Override public int compare(ResourceSchema resourceSchema, ResourceSchema resourceSchema2) { return resourceSchema.getName().compareTo(resourceSchema2.getName()); } }); entityNode.setSubresources(subresources); } return entityNode; } private void appendKeys(final AssociationSchema associationSchema, final ResourceModel collectionModel) { AssocKeySchemaArray assocKeySchemaArray = new AssocKeySchemaArray(); List<Key> sortedKeys = new ArrayList<Key>(collectionModel.getKeys()); Collections.sort(sortedKeys, new Comparator<Key>() { @Override public int compare(final Key o1, final Key o2) { return o1.getName().compareTo(o2.getName()); } }); for (Key key : sortedKeys) { AssocKeySchema assocKeySchema = new AssocKeySchema(); assocKeySchema.setName(key.getName()); assocKeySchema.setType(buildDataSchemaType(key.getType(), key.getDataSchema())); assocKeySchemaArray.add(assocKeySchema); } associationSchema.setAssocKeys(assocKeySchemaArray); associationSchema.setIdentifier(collectionModel.getKeyName()); } @SuppressWarnings("unchecked") private ActionSchemaArray createActions(final ResourceModel resourceModel, final ResourceLevel resourceLevel) { ActionSchemaArray actionsArray = new ActionSchemaArray(); List<ResourceMethodDescriptor> resourceMethodDescriptors = resourceModel.getResourceMethodDescriptors(); Collections.sort(resourceMethodDescriptors, new Comparator<ResourceMethodDescriptor>() { @Override public int compare(final ResourceMethodDescriptor o1, final ResourceMethodDescriptor o2) { if (o1.getType().equals(ResourceMethod.ACTION)) { if (o2.getType().equals(ResourceMethod.ACTION)) { return o1.getActionName().compareTo(o2.getActionName()); } else { return 1; } } else if (o2.getType().equals(ResourceMethod.ACTION)) { return -1; } else { return 0; } } }); for (ResourceMethodDescriptor resourceMethodDescriptor : resourceMethodDescriptors) { if (ResourceMethod.ACTION.equals(resourceMethodDescriptor.getType())) { //do not apply entity-level actions at collection level or vice-versa if (resourceMethodDescriptor.getActionResourceLevel() != resourceLevel) { continue; } ActionSchema action = new ActionSchema(); action.setName(resourceMethodDescriptor.getActionName()); String doc = _docsProvider.getMethodDoc(resourceMethodDescriptor.getMethod()); if (doc != null) { action.setDoc(doc); } ParameterSchemaArray parameters = createParameters(resourceMethodDescriptor); if (parameters.size() > 0) { action.setParameters(parameters); } Class<?> returnType = resourceMethodDescriptor.getActionReturnType(); if (returnType != Void.TYPE) { String returnTypeString = buildDataSchemaType(returnType, resourceMethodDescriptor .getActionReturnRecordDataSchema().getField(ActionResponse.VALUE_NAME).getType()); action.setReturns(returnTypeString); } final DataMap customAnnotation = resourceMethodDescriptor.getCustomAnnotationData(); String deprecatedDoc = _docsProvider.getMethodDeprecatedTag(resourceMethodDescriptor.getMethod()); if (deprecatedDoc != null) { customAnnotation.put(DEPRECATED_ANNOTATION_NAME, deprecateDocToAnnotationMap(deprecatedDoc)); } if (!customAnnotation.isEmpty()) { action.setAnnotations(new CustomAnnotationContentSchemaMap(customAnnotation)); } actionsArray.add(action); } } return actionsArray; } private DataMap deprecateDocToAnnotationMap(String deprecatedDoc) { deprecatedDoc = deprecatedDoc.trim(); DataMap deprecatedAnnotation = new DataMap(); if (!deprecatedDoc.isEmpty()) { deprecatedAnnotation.put(DEPRECATED_ANNOTATION_DOC_FIELD, deprecatedDoc); } return deprecatedAnnotation; } private FinderSchemaArray createFinders(final ResourceModel resourceModel) { FinderSchemaArray findersArray = new FinderSchemaArray(); List<ResourceMethodDescriptor> resourceMethodDescriptors = resourceModel.getResourceMethodDescriptors(); Collections.sort(resourceMethodDescriptors, new Comparator<ResourceMethodDescriptor>() { @Override public int compare(final ResourceMethodDescriptor o1, final ResourceMethodDescriptor o2) { if (o1.getFinderName() == null) { return -1; } else if (o2.getFinderName() == null) { return 1; } return o1.getFinderName().compareTo(o2.getFinderName()); } }); for (ResourceMethodDescriptor resourceMethodDescriptor : resourceMethodDescriptors) { if (ResourceMethod.FINDER.equals(resourceMethodDescriptor.getType())) { FinderSchema finder = new FinderSchema(); finder.setName(resourceMethodDescriptor.getFinderName()); String doc = _docsProvider.getMethodDoc(resourceMethodDescriptor.getMethod()); if (doc != null) { finder.setDoc(doc); } ParameterSchemaArray parameters = createParameters(resourceMethodDescriptor); if (parameters.size() > 0) { finder.setParameters(parameters); } StringArray assocKeys = createAssocKeyParameters(resourceMethodDescriptor); if (assocKeys.size() > 0) { finder.setAssocKeys(assocKeys); } if (resourceMethodDescriptor.getFinderMetadataType() != null) { Class<?> metadataType = resourceMethodDescriptor.getFinderMetadataType(); MetadataSchema metadataSchema = new MetadataSchema(); metadataSchema.setType(buildDataSchemaType(metadataType)); finder.setMetadata(metadataSchema); } final DataMap customAnnotation = resourceMethodDescriptor.getCustomAnnotationData(); String deprecatedDoc = _docsProvider.getMethodDeprecatedTag(resourceMethodDescriptor.getMethod()); if (deprecatedDoc != null) { customAnnotation.put(DEPRECATED_ANNOTATION_NAME, deprecateDocToAnnotationMap(deprecatedDoc)); } if (!customAnnotation.isEmpty()) { finder.setAnnotations(new CustomAnnotationContentSchemaMap(customAnnotation)); } findersArray.add(finder); } } return findersArray; } private StringArray createAssocKeyParameters(final ResourceMethodDescriptor resourceMethodDescriptor) { StringArray assocKeys = new StringArray(); for (Parameter<?> param : resourceMethodDescriptor.getParameters()) { // assocKeys are listed outside the parameters list if (param.getParamType() == Parameter.ParamType.KEY) { assocKeys.add(param.getName()); continue; } } return assocKeys; } private ParameterSchemaArray createParameters(final ResourceMethodDescriptor resourceMethodDescriptor) { ParameterSchemaArray parameterSchemaArray = new ParameterSchemaArray(); for (Parameter<?> param : resourceMethodDescriptor.getParameters()) { //only custom parameters need to be specified in the IDL if (!param.isCustom()) { continue; } // assocKeys are listed outside the parameters list if (param.getParamType() == Parameter.ParamType.KEY) { continue; } ParameterSchema paramSchema = new ParameterSchema(); paramSchema.setName(param.getName()); paramSchema.setType(buildDataSchemaType(param.getType(), param.getDataSchema())); final Object defaultValueData = param.getDefaultValueData(); if (defaultValueData == null && param.isOptional()) { paramSchema.setOptional(true); } else if (defaultValueData != null) { paramSchema.setDefault(defaultValueData.toString()); } String paramDoc = _docsProvider.getParamDoc(resourceMethodDescriptor.getMethod(), param.getName()); if (paramDoc != null) { paramSchema.setDoc(paramDoc); } final DataMap customAnnotation = param.getCustomAnnotationData(); if (!customAnnotation.isEmpty()) { paramSchema.setAnnotations(new CustomAnnotationContentSchemaMap(customAnnotation)); } parameterSchemaArray.add(paramSchema); } return parameterSchemaArray; } private RestMethodSchemaArray createRestMethods(final ResourceModel resourceModel) { RestMethodSchemaArray restMethods = new RestMethodSchemaArray(); ResourceMethod[] crudMethods = { ResourceMethod.CREATE, ResourceMethod.GET, ResourceMethod.UPDATE, ResourceMethod.PARTIAL_UPDATE, ResourceMethod.DELETE, ResourceMethod.BATCH_CREATE, ResourceMethod.BATCH_GET, ResourceMethod.BATCH_UPDATE, ResourceMethod.BATCH_PARTIAL_UPDATE, ResourceMethod.BATCH_DELETE, ResourceMethod.GET_ALL }; for (ResourceMethod method : crudMethods) { ResourceMethodDescriptor descriptor = resourceModel.findMethod(method); if (descriptor == null) { continue; } RestMethodSchema restMethod = new RestMethodSchema(); restMethod.setMethod(method.toString()); String doc = _docsProvider.getMethodDoc(descriptor.getMethod()); if (doc != null) { restMethod.setDoc(doc); } ParameterSchemaArray parameters = createParameters(descriptor); if (parameters.size() > 0) { restMethod.setParameters(parameters); } final DataMap customAnnotation = descriptor.getCustomAnnotationData(); String deprecatedDoc = _docsProvider.getMethodDeprecatedTag(descriptor.getMethod()); if (deprecatedDoc != null) { customAnnotation.put(DEPRECATED_ANNOTATION_NAME, deprecateDocToAnnotationMap(deprecatedDoc)); } if (!customAnnotation.isEmpty()) { restMethod.setAnnotations(new CustomAnnotationContentSchemaMap(customAnnotation)); } restMethods.add(restMethod); } return restMethods; } private void appendSupportsNodeToCollectionSchema(final CollectionSchema collectionSchema, final ResourceModel resourceModel) { StringArray supportsArray = buildSupportsNode(resourceModel); collectionSchema.setSupports(supportsArray); } private void appendMethodsToCollectionSchema(CollectionSchema collectionSchema, ResourceModel resourceModel) { RestMethodSchemaArray restMethods = createRestMethods(resourceModel); if (restMethods.size() > 0) { collectionSchema.setMethods(restMethods); } } private void appendSupportsNodeToSimpleSchema(final SimpleSchema simpleSchema, final ResourceModel resourceModel) { StringArray supportsArray = buildSupportsNode(resourceModel); simpleSchema.setSupports(supportsArray); } private void appendMethodsToSimpleSchema(SimpleSchema simpleSchema, ResourceModel resourceModel) { RestMethodSchemaArray restMethods = createRestMethods(resourceModel); if (restMethods.size() > 0) { simpleSchema.setMethods(restMethods); } } private StringArray buildSupportsNode(ResourceModel resourceModel) { StringArray supportsArray = new StringArray(); buildSupportsArray(resourceModel, supportsArray); return supportsArray; } private void buildSupportsArray(final ResourceModel resourceModel, final StringArray supportsArray) { List<String> supportsStrings = new ArrayList<String>(); for (ResourceMethodDescriptor resourceMethodDescriptor : resourceModel.getResourceMethodDescriptors()) { ResourceMethod type = resourceMethodDescriptor.getType(); if (!type.equals(ResourceMethod.FINDER) && !type.equals(ResourceMethod.ACTION)) { supportsStrings.add(type.toString()); } } Collections.sort(supportsStrings); for (String s : supportsStrings) { supportsArray.add(s); } } private void appendIdentifierNode(final CollectionSchema collectionNode, final ResourceModel collectionResource) { IdentifierSchema identifierSchema = new IdentifierSchema(); identifierSchema.setName(collectionResource.getKeyName()); // If the key is a complex key, set type to the schema type of the key part of the // complex key and params to that of the params part of the complex key. // Otherwise, just set the type to the key class schema type if (collectionResource.getKeyClass().equals(ComplexResourceKey.class)) { identifierSchema.setType(buildDataSchemaType(collectionResource.getKeyKeyClass())); identifierSchema.setParams(buildDataSchemaType(collectionResource.getKeyParamsClass())); } else { Key key = collectionResource.getPrimaryKey(); identifierSchema.setType(buildDataSchemaType(key.getType(), key.getDataSchema())); } collectionNode.setIdentifier(identifierSchema); } }