| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.apache.openjpa.persistence.meta; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.PrintWriter; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Date; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Scanner; |
| import java.util.Set; |
| |
| import javax.annotation.Generated; |
| import javax.annotation.processing.AbstractProcessor; |
| import javax.annotation.processing.ProcessingEnvironment; |
| import javax.annotation.processing.RoundEnvironment; |
| import javax.annotation.processing.SupportedAnnotationTypes; |
| import javax.annotation.processing.SupportedOptions; |
| import javax.lang.model.SourceVersion; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.element.TypeElement; |
| import javax.lang.model.type.TypeKind; |
| import javax.lang.model.type.TypeMirror; |
| import javax.lang.model.util.Elements; |
| import javax.persistence.metamodel.StaticMetamodel; |
| import javax.tools.Diagnostic; |
| import javax.tools.JavaFileObject; |
| |
| import org.apache.openjpa.lib.util.Localizer; |
| import org.apache.openjpa.meta.MetaDataFactory; |
| import org.apache.openjpa.persistence.PersistenceMetaDataFactory; |
| import org.apache.openjpa.persistence.PersistentCollection; |
| import org.apache.openjpa.persistence.util.SourceCode; |
| |
| |
| /** |
| * Annotation processing tool generates source code for a meta-model class given |
| * the annotated source code of persistent entity. |
| * <p> |
| * This tool is invoked during compilation for JDK6 compiler if |
| * <UL> |
| * <LI>OpenJPA and JPA libraries are available in the compiler classpath |
| * and <LI>Annotation Processor option <code>-Aopenjpa.metamodel=true</code> is specified. |
| * </UL> |
| * <br> |
| * <B>Usage</B><br> |
| * <code>$ javac -classpath path/to/openjpa-all.jar -Aopenjpa.metamodel=true mypackage/MyEntity.java</code><br> |
| * will generate source code for canonical meta-model class <code>mypackage.MyEntity_.java</code>. |
| * The source code is generated relative to the directory specified in <code>-s</code> option |
| * of <code>javac</code> compiler and defaulted to the current directory. |
| * <p> |
| * The Annotation Processor also recognizes the following options (none of them are mandatory):<br> |
| * <TABLE border="1"> |
| * <TR><TD>-Aopenjpa.log={log level}<TD>The logging level. Default is <code>WARN</code>. Permissible values are |
| * <code>TRACE</code>, <code>INFO</code>, <code>WARN</code> or <code> ERROR</code>. |
| * <TR><TD>-Aopenjpa.source={n} <TD>Java source version of the generated code. Default is <code>6</code>. |
| * <TR><TD>-Aopenjpa.naming={class name} <TD>fully-qualified name of a class implementing |
| * <code>org.apache.openjpa.meta.MetaDataFactory</code> that determines |
| * the name of a meta-class given the name of the original persistent Java entity class. Defaults to |
| * <code>org.apache.openjpa.persistence.PersistenceMetaDataFactory</code> which appends a underscore character |
| * (<code>_</code>) to the original Java class name. |
| * <TR><TD>-Aopenjpa.header={url} <TD> |
| * A url whose content will appear as comment header to the generated file(s). Recognizes special value |
| * <code>ASL</code> for Apache Source License header as comment. By default adds a OpenJPA proprietary |
| * text. |
| * </TABLE> |
| * <br> |
| * |
| * @author Pinaki Poddar |
| * |
| * @since 2.0.0 |
| * |
| */ |
| @SupportedAnnotationTypes({ |
| "javax.persistence.Entity", |
| "javax.persistence.Embeddable", |
| "javax.persistence.MappedSuperclass" }) |
| @SupportedOptions({ "openjpa.log", |
| "openjpa.source", |
| "openjpa.naming", |
| "openjpa.header", |
| "openjpa.metamodel" |
| }) |
| |
| public class AnnotationProcessor6 extends AbstractProcessor { |
| private SourceAnnotationHandler handler; |
| private MetaDataFactory factory; |
| private int generatedSourceVersion = 6; |
| private CompileTimeLogger logger; |
| private List<String> header = new ArrayList<String>(); |
| private boolean active; |
| private static Localizer _loc = Localizer.forPackage(AnnotationProcessor6.class); |
| private SourceVersion supportedSourceVersion; |
| |
| /** |
| * Category of members as per JPA 2.0 type system. |
| * |
| */ |
| private static enum TypeCategory { |
| ATTRIBUTE("javax.persistence.metamodel.SingularAttribute"), |
| COLLECTION("javax.persistence.metamodel.CollectionAttribute"), |
| SET("javax.persistence.metamodel.SetAttribute"), |
| LIST("javax.persistence.metamodel.ListAttribute"), |
| MAP("javax.persistence.metamodel.MapAttribute"); |
| |
| private String type; |
| |
| private TypeCategory(String type) { |
| this.type = type; |
| } |
| |
| public String getMetaModelType() { |
| return type; |
| } |
| } |
| |
| /** |
| * Enumerates available java.util.* collection classes to categorize them |
| * into corresponding JPA meta-model member type. |
| */ |
| private static List<String> CLASSNAMES_LIST = Arrays.asList( |
| new String[]{ |
| "java.util.List", "java.util.AbstractList", |
| "java.util.AbstractSequentialList", "java.util.ArrayList", |
| "java.util.Stack", "java.util.Vector"}); |
| private static List<String> CLASSNAMES_SET = Arrays.asList( |
| new String[]{ |
| "java.util.Set", "java.util.AbstractSet", "java.util.EnumSet", |
| "java.util.HashSet", "java.util.LinkedList", "java.util.LinkedHashSet", |
| "java.util.SortedSet", "java.util.TreeSet"}); |
| private static List<String> CLASSNAMES_MAP = Arrays.asList( |
| new String[]{ |
| "java.util.Map", "java.util.AbstractMap", "java.util.EnumMap", |
| "java.util.HashMap", "java.util.Hashtable", |
| "java.util.IdentityHashMap", "java.util.LinkedHashMap", |
| "java.util.Properties", "java.util.SortedMap", |
| "java.util.TreeMap"}); |
| private static List<String> CLASSNAMES_COLLECTION = Arrays.asList( |
| new String[]{ |
| "java.util.Collection", "java.util.AbstractCollection", |
| "java.util.AbstractQueue", "java.util.Queue", |
| "java.util.PriorityQueue"}); |
| |
| /** |
| * Gets the fully-qualified name of member class in JPA 2.0 type system, |
| * given the fully-qualified name of a Java class. |
| * |
| */ |
| private TypeCategory toMetaModelTypeCategory(TypeMirror mirror, |
| String name, boolean persistentCollection) { |
| if (mirror.getKind() == TypeKind.ARRAY && persistentCollection ) { |
| return TypeCategory.LIST; |
| } |
| if (CLASSNAMES_COLLECTION.contains(name)) |
| return TypeCategory.COLLECTION; |
| if (CLASSNAMES_LIST.contains(name)) |
| return TypeCategory.LIST; |
| if (CLASSNAMES_SET.contains(name)) |
| return TypeCategory.SET; |
| if (CLASSNAMES_MAP.contains(name)) |
| return TypeCategory.MAP; |
| return TypeCategory.ATTRIBUTE; |
| } |
| |
| @Override |
| public SourceVersion getSupportedSourceVersion() { |
| if (supportedSourceVersion != null) { |
| return supportedSourceVersion; |
| } |
| return SourceVersion.latestSupported(); |
| } |
| |
| /** |
| * Initialization. |
| */ |
| @Override |
| public synchronized void init(ProcessingEnvironment processingEnv) { |
| super.init(processingEnv); |
| active = "true".equalsIgnoreCase(getOptionValue("openjpa.metamodel")); |
| if (!active) |
| return; |
| |
| final String supported = getOptionValue("openjpa.processor.supportedversion"); |
| if (supported != null) { |
| supportedSourceVersion = SourceVersion.valueOf(supported); |
| } else { // default to ensure we don't log a false warning for every compilation, see OPENJPA-2300 |
| supportedSourceVersion = SourceVersion.latestSupported(); |
| } |
| |
| processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, _loc.get("mmg-tool-banner").toString()); |
| logger = new CompileTimeLogger(processingEnv, getOptionValue("openjpa.log")); |
| setSourceVersion(); |
| setNamingPolicy(); |
| setHeader(); |
| handler = new SourceAnnotationHandler(processingEnv, logger); |
| } |
| |
| /** |
| * The entry point for java compiler. |
| */ |
| @Override |
| public boolean process(Set<? extends TypeElement> annos, RoundEnvironment roundEnv) { |
| if (active && !roundEnv.processingOver()) { |
| Set<? extends Element> elements = roundEnv.getRootElements(); |
| for (Element e : elements) { |
| if (e instanceof TypeElement) { |
| process((TypeElement) e); |
| } |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Generate meta-model source code for the given type. |
| * |
| * @return true if code is generated for the given element. false otherwise. |
| */ |
| private boolean process(TypeElement e) { |
| if (!handler.isAnnotatedAsEntity(e)) { |
| return false; |
| } |
| |
| Elements eUtils = processingEnv.getElementUtils(); |
| String originalClass = eUtils.getBinaryName((TypeElement) e).toString(); |
| String originalSimpleClass = e.getSimpleName().toString(); |
| String metaClass = factory.getMetaModelClassName(originalClass); |
| |
| SourceCode source = new SourceCode(metaClass); |
| comment(source); |
| annotate(source, originalClass); |
| TypeElement supCls = handler.getPersistentSupertype(e); |
| if (supCls != null) { |
| String superName = factory.getMetaModelClassName(supCls.toString()); |
| source.getTopLevelClass().setSuper(superName); |
| } |
| try { |
| PrintWriter writer = createSourceFile(originalClass, metaClass, e); |
| SourceCode.Class modelClass = source.getTopLevelClass(); |
| Set<? extends Element> members = handler.getPersistentMembers(e); |
| |
| for (Element m : members) { |
| boolean isPersistentCollection = m.getAnnotation(PersistentCollection.class) != null; |
| |
| TypeMirror decl = handler.getDeclaredType(m); |
| String fieldName = handler.getPersistentMemberName(m); |
| String fieldType = handler.getDeclaredTypeName(decl, true, isPersistentCollection); |
| TypeCategory typeCategory = |
| toMetaModelTypeCategory(decl, fieldType, isPersistentCollection); |
| String metaModelType = typeCategory.getMetaModelType(); |
| SourceCode.Field modelField = null; |
| switch (typeCategory) { |
| case ATTRIBUTE: |
| modelField = modelClass.addField(fieldName, metaModelType); |
| modelField.addParameter(originalSimpleClass) |
| .addParameter(fieldType); |
| break; |
| case COLLECTION: |
| case LIST: |
| case SET: |
| TypeMirror param = handler.getTypeParameter(m, decl, 0, true); |
| String elementType = handler.getDeclaredTypeName(param); |
| modelField = modelClass.addField(fieldName, metaModelType); |
| modelField.addParameter(originalSimpleClass) |
| .addParameter(elementType); |
| break; |
| case MAP: |
| TypeMirror key = handler.getTypeParameter(m, decl, 0, false); |
| TypeMirror value = handler.getTypeParameter(m, decl, 1, true); |
| String keyType = handler.getDeclaredTypeName(key); |
| String valueType = handler.getDeclaredTypeName(value); |
| modelField = modelClass.addField(fieldName, metaModelType); |
| modelField.addParameter(originalSimpleClass) |
| .addParameter(keyType) |
| .addParameter(valueType); |
| break; |
| } |
| modelField.makePublic().makeStatic().makeVolatile(); |
| } |
| source.write(writer); |
| writer.flush(); |
| writer.close(); |
| return true; |
| } catch (Exception e1) { |
| logger.error(_loc.get("mmg-process-error", e.getQualifiedName()), e1); |
| return false; |
| } |
| } |
| |
| private void annotate(SourceCode source, String originalClass) { |
| SourceCode.Class cls = source.getTopLevelClass(); |
| cls.addAnnotation(StaticMetamodel.class.getName()) |
| .addArgument("value", originalClass + ".class", false); |
| if (generatedSourceVersion >= 6) { |
| cls.addAnnotation(Generated.class.getName()) |
| .addArgument("value", this.getClass().getName()) |
| .addArgument("date", new Date().toString()); |
| } |
| } |
| |
| private void comment(SourceCode source) { |
| if (header.size() != 0) |
| source.addComment(false, header.toArray(new String[header.size()])); |
| String defaultHeader = _loc.get("mmg-tool-sign").getMessage(); |
| source.addComment(false, defaultHeader); |
| } |
| |
| /** |
| * Parse annotation processor option <code>-Aopenjpa.source=n</code> to detect |
| * the source version for the generated classes. |
| * n must be a integer. Default or wrong specification returns 6. |
| */ |
| private void setSourceVersion() { |
| String version = getOptionValue("openjpa.source"); |
| if (version != null) { |
| try { |
| generatedSourceVersion = Integer.parseInt(version); |
| } catch (NumberFormatException e) { |
| logger.warn(_loc.get("mmg-bad-source", version, 6)); |
| generatedSourceVersion = 6; |
| } |
| } else { |
| generatedSourceVersion = 6; |
| } |
| } |
| |
| private void setNamingPolicy() { |
| String policy = getOptionValue("openjpa.naming"); |
| if (policy != null) { |
| try { |
| factory = (MetaDataFactory)Class.forName(policy).newInstance(); |
| } catch (Throwable e) { |
| logger.warn(_loc.get("mmg-bad-naming", policy, e)); |
| factory = new PersistenceMetaDataFactory(); |
| } |
| } else { |
| factory = new PersistenceMetaDataFactory(); |
| } |
| } |
| |
| private void setHeader() { |
| String headerOption = getOptionValue("openjpa.header"); |
| if (headerOption == null) { |
| return; |
| } |
| if ("ASL".equalsIgnoreCase(headerOption)) { |
| header.add(_loc.get("mmg-asl-header").getMessage()); |
| } else { |
| try { |
| URL url = new URL(headerOption); |
| InputStream is = url.openStream(); |
| Scanner s = new Scanner(is); |
| while (s.hasNextLine()) { |
| header.add(s.nextLine()); |
| } |
| } catch (Throwable t) { |
| |
| } |
| } |
| } |
| |
| /** |
| * Creates a file where source code of the given metaClass will be written. |
| * |
| */ |
| private PrintWriter createSourceFile(String originalClass, String metaClass, TypeElement e) |
| throws IOException { |
| JavaFileObject javaFile = processingEnv.getFiler().createSourceFile(metaClass, e); |
| logger.info(_loc.get("mmg-process", javaFile.toUri().normalize())); |
| return new PrintWriter(javaFile.openWriter()); |
| } |
| |
| /** |
| * Get the value for the given keys, whoever matches first, in the current available options. |
| */ |
| private String getOptionValue(String... keys) { |
| Map<String,String> options = processingEnv.getOptions(); |
| for (String key : keys) { |
| if (options.containsKey(key)) |
| return options.get(key); |
| } |
| return null; |
| } |
| } |