Java tutorial
/* * Copyright 2013 (c) MuleSoft, Inc. * * 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 org.raml.jaxrs.codegen.core; import static org.raml.jaxrs.codegen.core.Constants.JAXRS_HTTP_METHODS; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintStream; import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.net.URL; import java.util.AbstractMap.SimpleEntry; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.ws.rs.HttpMethod; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.io.output.ByteArrayOutputStream; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; import org.jsonschema2pojo.AnnotatorFactory; import org.jsonschema2pojo.GenerationConfig; import org.jsonschema2pojo.SchemaGenerator; import org.jsonschema2pojo.SchemaMapper; import org.jsonschema2pojo.SchemaStore; import org.jsonschema2pojo.rules.RuleFactory; import org.raml.model.Raml; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.io.Files; import com.sun.codemodel.JAnnotatable; import com.sun.codemodel.JClass; import com.sun.codemodel.JClassAlreadyExistsException; import com.sun.codemodel.JCodeModel; import com.sun.codemodel.JDefinedClass; import com.sun.codemodel.JMethod; import com.sun.codemodel.JMod; import com.sun.codemodel.JPackage; import com.sun.codemodel.JType; class Context { private static final Logger LOGGER = LoggerFactory.getLogger(Context.class); private final Configuration configuration; private final Raml raml; private final JCodeModel codeModel; private final Map<String, Set<String>> resourcesMethods; private final Map<String, Object> httpMethodAnnotations; private final SchemaMapper schemaMapper; private boolean shouldGenerateResponseWrapper = false; private JDefinedClass currentResourceInterface; private final File globalSchemaStore; public Context(final Configuration configuration, final Raml raml) throws IOException { Validate.notNull(configuration, "configuration can't be null"); Validate.notNull(raml, "raml can't be null"); this.configuration = configuration; this.raml = raml; codeModel = new JCodeModel(); resourcesMethods = new HashMap<String, Set<String>>(); // prime the HTTP method annotation cache httpMethodAnnotations = new HashMap<String, Object>(); for (final Class<? extends Annotation> clazz : JAXRS_HTTP_METHODS) { httpMethodAnnotations.put(clazz.getSimpleName(), clazz); } // write all global schemas to a temporary directory globalSchemaStore = Files.createTempDir(); for (final Entry<String, String> nameAndSchema : raml.getConsolidatedSchemas().entrySet()) { final File schemaFile = new File(globalSchemaStore, nameAndSchema.getKey()); FileUtils.writeStringToFile(schemaFile, nameAndSchema.getValue()); } // configure the JSON -> POJO generator final GenerationConfig jsonSchemaGenerationConfig = configuration.createJsonSchemaGenerationConfig(); schemaMapper = new SchemaMapper( new RuleFactory(jsonSchemaGenerationConfig, new AnnotatorFactory().getAnnotator(configuration.getJsonMapper()), new SchemaStore()), new SchemaGenerator()); } public Set<String> generate() throws IOException { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final PrintStream ps = new PrintStream(baos); codeModel.build(configuration.getOutputDirectory(), ps); ps.close(); final Set<String> generatedFiles = new HashSet<String>(); if (shouldGenerateResponseWrapper) { generatedFiles.add(generateResponseWrapper()); } generatedFiles.addAll(Arrays.asList(StringUtils.split(baos.toString()))); try { FileUtils.deleteDirectory(globalSchemaStore); } catch (final Exception e) { LOGGER.warn("Failed to delete temporary directory: " + globalSchemaStore); } return generatedFiles; } /** * @return a {schema file, schema name} tuple. */ public Entry<File, String> getSchemaFile(final String schemaNameOrContent) throws IOException { if (raml.getConsolidatedSchemas().containsKey(schemaNameOrContent)) { // schemaNameOrContent is actually a global name return new SimpleEntry<File, String>(new File(globalSchemaStore, schemaNameOrContent), schemaNameOrContent); } else { // this is not a global reference but a local schema def - dump it to a temp file so // the type generators can pick it up final String schemaFileName = "schema" + schemaNameOrContent.hashCode(); final File schemaFile = new File(globalSchemaStore, schemaFileName); FileUtils.writeStringToFile(schemaFile, schemaNameOrContent); return new SimpleEntry<File, String>(schemaFile, null); } } public Configuration getConfiguration() { return configuration; } public JDefinedClass getCurrentResourceInterface() { return currentResourceInterface; } public void setCurrentResourceInterface(final JDefinedClass currentResourceInterface) { this.currentResourceInterface = currentResourceInterface; } private String generateResponseWrapper() throws IOException { final String template = IOUtils .toString(getClass().getResourceAsStream("/org/raml/templates/ResponseWrapper." + configuration.getJaxrsVersion().toString().toLowerCase() + ".template")); final File supportPackageOutputDirectory = new File(configuration.getOutputDirectory(), getSupportPackage().replace('.', File.separatorChar)); supportPackageOutputDirectory.mkdirs(); final File sourceOutputFile = new File(supportPackageOutputDirectory, "ResponseWrapper.java"); final String source = template.replace("${codegen.support.package}", getSupportPackage()); final FileWriter fileWriter = new FileWriter(sourceOutputFile); IOUtils.write(source, fileWriter); IOUtils.closeQuietly(fileWriter); return getSupportPackage().replace('.', '/') + "/ResponseWrapper.java"; } public JClass getResponseWrapperType() { shouldGenerateResponseWrapper = true; return codeModel.directClass(getSupportPackage() + ".ResponseWrapper"); } public JDefinedClass createResourceInterface(final String name) throws Exception { String actualName; int i = -1; while (true) { actualName = name + (++i == 0 ? "" : Integer.toString(i)); if (!resourcesMethods.containsKey(actualName)) { resourcesMethods.put(actualName, new HashSet<String>()); break; } } final JPackage pkg = codeModel._package(configuration.getBasePackageName() + ".resource"); return pkg._interface(actualName); } public JMethod createResourceMethod(final JDefinedClass resourceInterface, final String methodName, final JType returnType) { final Set<String> existingMethodNames = resourcesMethods.get(resourceInterface.name()); String actualMethodName; int i = -1; while (true) { actualMethodName = methodName + (++i == 0 ? "" : Integer.toString(i)); if (!existingMethodNames.contains(actualMethodName)) { existingMethodNames.add(actualMethodName); break; } } return resourceInterface.method(JMod.NONE, returnType, actualMethodName); } public JDefinedClass createResourceEnum(final JDefinedClass resourceInterface, final String name, final List<String> values) throws Exception { JClass[] listClasses = resourceInterface.listClasses(); for (JClass c : listClasses) { if (c.name().equals(name)) { return (JDefinedClass) c; } } final JDefinedClass _enum = resourceInterface._enum(name); for (final String value : values) { _enum.enumConstant(value); } return _enum; } public JClass getGeneratorClass(final String clazzFQN) { return codeModel.ref(clazzFQN); } @SuppressWarnings("unchecked") public Context addHttpMethodAnnotation(final String httpMethod, final JAnnotatable annotatable) throws Exception { final Object annotationClass = httpMethodAnnotations.get(httpMethod.toUpperCase()); if (annotationClass == null) { final JDefinedClass annotationClazz = createCustomHttpMethodAnnotation(httpMethod); annotatable.annotate(annotationClazz); } else if (annotationClass instanceof JClass) { annotatable.annotate((JClass) annotationClass); } else if (annotationClass instanceof Class) { annotatable.annotate((Class<? extends Annotation>) annotationClass); } else { throw new IllegalStateException( "Found annotation: " + annotationClass + " for HTTP method: " + httpMethod); } return this; } public JType getGeneratorType(final Class<?> clazz) { return clazz.isPrimitive() ? JType.parse(codeModel, clazz.getSimpleName()) : codeModel.ref(clazz); } public JClass generateClassFromJsonSchema(final String className, final URL schemaUrl) throws IOException { return schemaMapper.generate(codeModel, className, getModelPackage(), schemaUrl).boxify(); } private JDefinedClass createCustomHttpMethodAnnotation(final String httpMethod) throws JClassAlreadyExistsException { final JPackage pkg = codeModel._package(getSupportPackage()); final JDefinedClass annotationClazz = pkg._annotationTypeDeclaration(httpMethod); annotationClazz.annotate(Target.class).param("value", ElementType.METHOD); annotationClazz.annotate(Retention.class).param("value", RetentionPolicy.RUNTIME); annotationClazz.annotate(HttpMethod.class).param("value", httpMethod); annotationClazz.javadoc().add("Custom JAX-RS support for HTTP " + httpMethod + "."); httpMethodAnnotations.put(httpMethod.toUpperCase(), annotationClazz); return annotationClazz; } private String getModelPackage() { return configuration.getBasePackageName() + ".model"; } private String getSupportPackage() { return configuration.getBasePackageName() + ".support"; } }