/** * Copyright 2005-2014 Restlet * * The contents of this file are subject to the terms of one of the following * open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can * select the license that you prefer but you may not use this file except in * compliance with one of these Licenses. * * You can obtain a copy of the Apache 2.0 license at * http://www.opensource.org/licenses/apache-2.0 * * You can obtain a copy of the EPL 1.0 license at * http://www.opensource.org/licenses/eclipse-1.0 * * See the Licenses for the specific language governing permissions and * limitations under the Licenses. * * Alternatively, you can obtain a royalty free commercial license with less * limitations, transferable or non-transferable, directly at * http://restlet.com/products/restlet-framework * * Restlet is a registered trademark of Restlet S.A.S. */ package org.restlet.ext.apispark.internal.introspection.application; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.logging.Logger; import org.restlet.data.ChallengeScheme; import org.restlet.data.Form; import org.restlet.data.Method; import org.restlet.data.Status; import org.restlet.engine.application.StatusInfo; import org.restlet.engine.resource.AnnotationInfo; import org.restlet.engine.resource.AnnotationUtils; import org.restlet.engine.resource.MethodAnnotationInfo; import org.restlet.engine.resource.ThrowableAnnotationInfo; import org.restlet.engine.util.StringUtils; import org.restlet.ext.apispark.Introspector; import org.restlet.ext.apispark.internal.introspection.DocumentedResource; import org.restlet.ext.apispark.internal.introspection.IntrospectionHelper; import org.restlet.ext.apispark.internal.introspection.util.TypeInfo; import org.restlet.ext.apispark.internal.introspection.util.Types; import org.restlet.ext.apispark.internal.introspection.util.UnsupportedTypeException; import org.restlet.ext.apispark.internal.model.Operation; import org.restlet.ext.apispark.internal.model.PathVariable; import org.restlet.ext.apispark.internal.model.PayLoad; import org.restlet.ext.apispark.internal.model.QueryParameter; import org.restlet.ext.apispark.internal.model.Resource; import org.restlet.ext.apispark.internal.model.Response; import org.restlet.ext.apispark.internal.model.Section; import org.restlet.representation.Variant; import org.restlet.resource.Directory; import org.restlet.resource.ResourceException; import org.restlet.resource.ServerResource; import org.restlet.routing.Template; import org.restlet.service.MetadataService; /** * @author Manuel Boillod */ public class ResourceCollector { /** Internal logger. */ protected static Logger LOGGER = Logger.getLogger(Introspector.class .getName()); private static final String SUFFIX_RESOURCE = "Resource"; private static final String SUFFIX_SERVER_RESOURCE = "ServerResource"; public static void collectResource(CollectInfo collectInfo, Directory directory, String basePath, ChallengeScheme scheme, List<? extends IntrospectionHelper> introspectionHelper) { Resource resource = getResource(collectInfo, directory, basePath, scheme); // add operations ArrayList<Operation> operations = new ArrayList<>(); operations.add(getOperationFromMethod(Method.GET)); if (directory.isModifiable()) { operations.add(getOperationFromMethod(Method.DELETE)); operations.add(getOperationFromMethod(Method.PUT)); } resource.setOperations(operations); for (IntrospectionHelper helper : introspectionHelper) { helper.processResource(resource, directory.getClass()); } addSectionsForResource(collectInfo, resource); collectInfo.addResource(resource); } public static void collectResource( CollectInfo collectInfo, ServerResource sr, String basePath, ChallengeScheme scheme, List<? extends IntrospectionHelper> introspectionHelper) { Resource resource = getResource(collectInfo, sr, basePath, scheme); // add operations ArrayList<Operation> operations = new ArrayList<>(); List<AnnotationInfo> annotations = sr.isAnnotated() ? AnnotationUtils .getInstance().getAnnotations(sr.getClass()) : null; if (annotations != null) { for (AnnotationInfo annotationInfo : annotations) { if (annotationInfo instanceof MethodAnnotationInfo) { MethodAnnotationInfo methodAnnotationInfo = (MethodAnnotationInfo) annotationInfo; Method method = methodAnnotationInfo.getRestletMethod(); Operation operation = getOperationFromMethod(method); if (StringUtils.isNullOrEmpty(operation.getName())) { operation.setName(methodAnnotationInfo.getJavaMethod() .getName()); } completeOperation(collectInfo, operation, methodAnnotationInfo, sr, introspectionHelper); for (IntrospectionHelper helper : introspectionHelper) { List<Class<?>> representationClasses = helper.processOperation(resource, operation, sr.getClass(), methodAnnotationInfo.getJavaMethod()); if (representationClasses != null && !representationClasses.isEmpty()) { for (Class<?> representationClazz : representationClasses) { TypeInfo typeInfo; try { typeInfo = Types.getTypeInfo(representationClazz, null); } catch (UnsupportedTypeException e) { LOGGER.warning("Could not add representation class " + representationClazz.getName() + ". " + e.getMessage()); continue; } RepresentationCollector.addRepresentation(collectInfo, typeInfo, introspectionHelper); } } } operations.add(operation); } } if (!operations.isEmpty()) { sortOperationsByMethod(operations); resource.setOperations(operations); addSectionsForResource(collectInfo, resource); collectInfo.addResource(resource); } else { LOGGER.warning("Resource " + resource.getName() + " has no methods."); } } else { LOGGER.warning("Resource " + resource.getName() + " has no methods."); } for (IntrospectionHelper helper : introspectionHelper) { helper.processResource(resource, sr.getClass()); } } /** * Automatically describes a method by discovering the resource's * annotations. * */ private static void completeOperation(CollectInfo collectInfo, Operation operation, MethodAnnotationInfo mai, ServerResource sr, List<? extends IntrospectionHelper> introspectionHelper) { // Loop over the annotated Java methods MetadataService metadataService = sr.getMetadataService(); // Retrieve thrown classes completeOperationThrows(collectInfo, operation, mai, introspectionHelper); // Describe the input completeOperationInput(collectInfo, operation, mai, sr, introspectionHelper, metadataService); // Describe query parameters, if any. completeOperationQueryParameter(operation, mai); // Describe the success response completeOperationOutput(collectInfo, operation, mai, introspectionHelper); // Produces completeOperationProduces(operation, mai, sr, metadataService); } private static void completeOperationThrows(CollectInfo collectInfo, Operation operation, MethodAnnotationInfo mai, List<? extends IntrospectionHelper> introspectionHelper) { Class<?>[] thrownClasses = mai.getJavaMethod().getExceptionTypes(); if (thrownClasses != null) { for (Class<?> thrownClass : thrownClasses) { ThrowableAnnotationInfo throwableAnnotationInfo = AnnotationUtils .getInstance().getThrowableAnnotationInfo(thrownClass); if (throwableAnnotationInfo != null) { int statusCode = throwableAnnotationInfo.getStatus().getCode(); Response response = new Response(); response.setCode(statusCode); response.setName(Status.valueOf(statusCode).getReasonPhrase()); response.setMessage("Status " + statusCode); Class<?> outputPayloadType = throwableAnnotationInfo .isSerializable() ? thrownClass : StatusInfo.class; TypeInfo outputTypeInfo = null; try { outputTypeInfo = Types.getTypeInfo(outputPayloadType, null); } catch (UnsupportedTypeException e) { LOGGER.warning("Could not add output payload for exception " + thrownClass + " throws by method " + mai.getJavaMethod() + ". " + e.getMessage()); continue; } RepresentationCollector.addRepresentation(collectInfo, outputTypeInfo, introspectionHelper); PayLoad outputPayLoad = new PayLoad(); outputPayLoad.setType(outputTypeInfo.getRepresentationName()); response.setOutputPayLoad(outputPayLoad); operation.getResponses().add(response); } } } } private static void completeOperationInput(CollectInfo collectInfo, Operation operation, MethodAnnotationInfo mai, ServerResource sr, List<? extends IntrospectionHelper> introspectionHelper, MetadataService metadataService) { Class<?>[] inputClasses = mai.getJavaMethod().getParameterTypes(); if (inputClasses != null && inputClasses.length > 0) { // Input representation // Handles only the first method parameter TypeInfo inputTypeInfo; try { inputTypeInfo = Types.getTypeInfo(inputClasses[0], mai.getJavaMethod().getGenericParameterTypes()[0]); } catch (UnsupportedTypeException e) { LOGGER.warning("Could not add input representation of method" + mai.getJavaMethod() + ". " + e.getMessage()); return; } RepresentationCollector.addRepresentation(collectInfo, inputTypeInfo, introspectionHelper); PayLoad inputEntity = new PayLoad(); inputEntity.setType(inputTypeInfo.getRepresentationName()); inputEntity.setArray(inputTypeInfo.isList()); operation.setInputPayLoad(inputEntity); // Consumes if (metadataService != null) { try { List<Variant> requestVariants = mai.getRequestVariants( metadataService, sr.getConverterService()); if (requestVariants == null || requestVariants.isEmpty()) { LOGGER.warning("Could not add consumes of method " + mai.getJavaMethod() + ". There is no requested variant"); return; } // une representation per variant ? for (Variant variant : requestVariants) { if (variant.getMediaType() == null) { LOGGER.warning("Variant has no media type: " + variant); continue; } operation.getConsumes().add( variant.getMediaType().getName()); } } catch (IOException e) { throw new ResourceException(e); } } } } private static void completeOperationQueryParameter(Operation operation, MethodAnnotationInfo mai) { if (mai.getQuery() != null) { Form form = new Form(mai.getQuery()); for (org.restlet.data.Parameter parameter : form) { QueryParameter queryParameter = new QueryParameter(); queryParameter.setName(parameter.getName()); queryParameter.setRequired(true); queryParameter.setDescription(StringUtils .isNullOrEmpty(parameter.getValue()) ? "" : "Value: " + parameter.getValue()); queryParameter.setDefaultValue(parameter.getValue()); queryParameter.setAllowMultiple(false); operation.getQueryParameters().add(queryParameter); } } } private static void completeOperationOutput(CollectInfo collectInfo, Operation operation, MethodAnnotationInfo mai, List<? extends IntrospectionHelper> introspectionHelper) { Response response = new Response(); if (mai.getJavaMethod().getReturnType() != Void.TYPE) { TypeInfo outputTypeInfo; try { outputTypeInfo = Types.getTypeInfo(mai.getJavaMethod().getReturnType(), mai.getJavaMethod().getGenericReturnType()); } catch (UnsupportedTypeException e) { LOGGER.warning("Could not add output representation of method " + mai.getJavaMethod() + ". " + e.getMessage()); return; } // Output representation RepresentationCollector.addRepresentation(collectInfo, outputTypeInfo, introspectionHelper); PayLoad outputEntity = new PayLoad(); outputEntity.setType(outputTypeInfo.getRepresentationName()); outputEntity.setArray(outputTypeInfo.isList()); response.setOutputPayLoad(outputEntity); response.setCode(Status.SUCCESS_OK.getCode()); response.setName(Status.SUCCESS_OK.getReasonPhrase()); response.setDescription(""); response.setMessage(Status.SUCCESS_OK.getDescription()); } else { response.setCode(Status.SUCCESS_NO_CONTENT.getCode()); response.setName(Status.SUCCESS_NO_CONTENT.getReasonPhrase()); response.setDescription(""); response.setMessage(Status.SUCCESS_NO_CONTENT.getDescription()); } operation.getResponses().add(response); } private static void completeOperationProduces(Operation operation, MethodAnnotationInfo mai, ServerResource sr, MetadataService metadataService) { if (metadataService != null) { try { List<Variant> responseVariants = mai.getResponseVariants( metadataService, sr.getConverterService()); if (responseVariants == null || responseVariants.isEmpty()) { if (mai.getJavaMethod().getReturnType() != Void.TYPE) { LOGGER.warning("Method has no response variant: " + mai.getJavaMethod()); } return; } // une representation per variant ? for (Variant variant : responseVariants) { if (variant.getMediaType() == null) { LOGGER.warning("Variant has no media type: " + variant); continue; } operation.getProduces().add( variant.getMediaType().getName()); } } catch (IOException e) { throw new ResourceException(e); } } } private static Operation getOperationFromMethod(Method method) { Operation operation = new Operation(); operation.setMethod(method.getName()); return operation; } private static Resource getResource(CollectInfo collectInfo, Object restlet, String basePath, ChallengeScheme scheme) { Resource resource = new Resource(); resource.setResourcePath(basePath); if (restlet instanceof Directory) { Directory directory = (Directory) restlet; resource.setName(directory.getName()); resource.setDescription(directory.getDescription()); } if (restlet instanceof ServerResource) { ServerResource serverResource = (ServerResource) restlet; resource.setName(serverResource.getName()); resource.setDescription(serverResource.getDescription()); } if (restlet instanceof DocumentedResource) { DocumentedResource documentedServerResource = (DocumentedResource) restlet; resource.setSections(documentedServerResource.getSections()); } else if (collectInfo.isUseSectionNamingPackageStrategy()) { String sectionName = restlet.getClass().getPackage().getName(); resource.getSections().add(sectionName); } if (StringUtils.isNullOrEmpty(resource.getName())) { String name = restlet.getClass().getSimpleName(); if (name.endsWith(SUFFIX_SERVER_RESOURCE) && name.length() > SUFFIX_SERVER_RESOURCE.length()) { name = name.substring(0, name.length() - SUFFIX_SERVER_RESOURCE.length()); } if (name.endsWith(SUFFIX_RESOURCE) && name.length() > SUFFIX_RESOURCE.length()) { name = name.substring(0, name.length() - SUFFIX_RESOURCE.length()); } resource.setName(name); } Template template = new Template(basePath); for (String variable : template.getVariableNames()) { PathVariable pathVariable = new PathVariable(); pathVariable.setName(variable); resource.getPathVariables().add(pathVariable); } if (scheme != null) { resource.setAuthenticationProtocol(scheme.getName()); } return resource; } private static void addSectionsForResource(CollectInfo collectInfo, Resource resource) { for (String section : resource.getSections()) { if (collectInfo.getSection(section) == null) { collectInfo.addSection(new Section(section)); } } } private static void sortOperationsByMethod(ArrayList<Operation> operations) { Collections.sort(operations, new Comparator<Operation>() { public int compare(Operation o1, Operation o2) { int c = o1.getMethod().compareTo(o2.getMethod()); if (c == 0) { c = o1.getName().compareTo(o2.getName()); } return c; } }); } }