/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Rodrigo Westrupp */ package com.caucho.amber.cfg; import com.caucho.amber.field.IdField; import com.caucho.amber.table.ForeignColumn; import com.caucho.amber.type.BeanType; import com.caucho.amber.type.EntityType; import com.caucho.config.ConfigException; import com.caucho.util.L10N; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.logging.Logger; import java.lang.annotation.Annotation; import java.lang.reflect.*; import javax.persistence.JoinColumn; import javax.persistence.Version; /** * Abstract introspector for orm.xml and annotations. */ abstract public class AbstractConfigIntrospector { private static final Logger log = Logger.getLogger(AbstractConfigIntrospector.class.getName()); private static final L10N L = new L10N(AbstractConfigIntrospector.class); // annotations allowed for a property static HashSet<String> _propertyAnnotations = new HashSet<String>(); // types allowed with a @Basic annotation static HashSet<String> _basicTypes = new HashSet<String>(); // annotations allowed with a @Basic annotation static HashSet<String> _basicAnnotations = new HashSet<String>(); // types allowed with an @Id annotation static HashSet<String> _idTypes = new HashSet<String>(); // annotations allowed with an @Id annotation static HashSet<String> _idAnnotations = new HashSet<String>(); // annotations allowed with a @ManyToOne annotation static HashSet<String> _manyToOneAnnotations = new HashSet<String>(); // annotations allowed with a @OneToMany annotation static HashSet<String> _oneToManyAnnotations = new HashSet<String>(); // types allowed with a @OneToMany annotation static HashSet<String> _oneToManyTypes = new HashSet<String>(); // annotations allowed with a @ManyToMany annotation static HashSet<String> _manyToManyAnnotations = new HashSet<String>(); // types allowed with a @ManyToMany annotation static HashSet<String> _manyToManyTypes = new HashSet<String>(); // annotations allowed with a @OneToOne annotation static HashSet<String> _oneToOneAnnotations = new HashSet<String>(); // annotations allowed with a @ElementCollection annotation static HashSet<String> _elementCollectionAnnotations = new HashSet<String>(); // types allowed with a @ElementCollection annotation static HashSet<String> _elementCollectionTypes = new HashSet<String>(); // annotations allowed with a @Embedded annotation static HashSet<String> _embeddedAnnotations = new HashSet<String>(); // annotations allowed with a @EmbeddedId annotation static HashSet<String> _embeddedIdAnnotations = new HashSet<String>(); // annotations allowed with a @Version annotation static HashSet<String> _versionAnnotations = new HashSet<String>(); // types allowed with an @Version annotation static HashSet<String> _versionTypes = new HashSet<String>(); AnnotationConfig _annotationCfg = new AnnotationConfig(); /** * Validates a callback method */ void validateCallback(String callbackName, Method method, boolean isListener) throws ConfigException { if (Modifier.isFinal(method.getModifiers())) throw error(method, L.l("'{0}' must not be final. @{1} methods may not be final.", getFullName(method), callbackName)); if (Modifier.isStatic(method.getModifiers())) throw error(method, L.l("'{0}' must not be static. @{1} methods may not be static.", getFullName(method), callbackName)); Class params[] = method.getParameterTypes(); if (isListener) { if (params.length != 1) { throw error(method, L.l("'{0}' must have the <METHOD>(Object) signature for entity listeners.", getFullName(method))); } } else if (params.length != 0) { throw error(method, L.l("'{0}' must not have any arguments. @{1} methods have zero arguments for entities or mapped superclasses.", getFullName(method), callbackName)); } } /** * Validates the bean */ public void validateType(Class type, boolean isEntity) throws ConfigException { if (Modifier.isFinal(type.getModifiers())) throw new ConfigException(L.l("'{0}' must not be final. Entity beans may not be final.", type.getName())); // NOTE: Both abstract and concrete classes can be entities. // MappedSuperclass does not need constructor validation. if (isEntity) validateConstructor(type); for (Method method : type.getMethods()) { if (method.getDeclaringClass().getName().equals("java.lang.Object")) { } else if (Modifier.isFinal(method.getModifiers())) throw error(method, L.l("'{0}' must not be final. Entity beans methods may not be final.", getFullName(method))); } } /** * Checks for a valid constructor. */ public void validateConstructor(Class type) throws ConfigException { for (Constructor ctor : type.getDeclaredConstructors()) { Class []param = ctor.getParameterTypes(); if (param.length == 0 && (Modifier.isPublic(ctor.getModifiers()) || Modifier.isProtected(ctor.getModifiers()))) return; } // jpa/0gb2 throw new ConfigException(L.l("'{0}' needs a public or protected no-arg constructor. Entity beans must have a public or protected no-arg constructor.", type.getName())); } /** * Validates a non-getter method. */ public void validateNonGetter(Method method) throws ConfigException { Annotation ann = isAnnotatedMethod(method); if (ann != null && ! (ann instanceof Version)) { throw error(method, L.l("'{0}' is not a valid annotation for {1}. Only public getters and fields may have property annotations.", ann, getFullName(method))); } } /** * Validates a non-getter method. */ Annotation isAnnotatedMethod(Method method) throws ConfigException { for (Annotation ann : method.getDeclaredAnnotations()) { if (_propertyAnnotations.contains(ann.getClass().getName())) { return ann; } } return null; } static boolean containsFieldOrCompletion(BeanType type, String fieldName) { // jpa/0l03 while (type != null) { if (type.getField(fieldName) != null) return true; if (type.containsCompletionField(fieldName)) return true; if (type instanceof EntityType) type = ((EntityType) type).getParentType(); } return false; } static void validateAnnotations(AccessibleObject field, String fieldName, String fieldType, HashSet<String> validAnnotations) throws ConfigException { for (Annotation ann : field.getDeclaredAnnotations()) { String name = ann.getClass().getName(); if (! name.startsWith("javax.persistence")) continue; if (! validAnnotations.contains(name)) { throw error(field, L.l("{0} may not have a @{1} annotation. {2} does not allow @{3}.", fieldName, name, fieldType, name)); } } } static String getFullName(Method method) { return method.getName(); } static String toFieldName(String name) { // jpa/0g0d return Character.toLowerCase(name.charAt(0)) + name.substring(1); /* if (Character.isLowerCase(name.charAt(0))) return name; else if (name.length() == 1 || ! Character.isUpperCase(name.charAt(1))) return Character.toLowerCase(name.charAt(0)) + name.substring(1); else return name; */ } static ArrayList<ForeignColumn> calculateColumns(com.caucho.amber.table.AmberTable mapTable, EntityType type, JoinColumn []joinColumns) { if (joinColumns == null || joinColumns.length == 0) return calculateColumns(mapTable, type); ArrayList<ForeignColumn> columns = new ArrayList<ForeignColumn>(); for (int i = 0; i < joinColumns.length; i++) { ForeignColumn foreignColumn; JoinColumn joinColumn = joinColumns[i]; foreignColumn = mapTable.createForeignColumn(joinColumn.name(), type.getId().getKey().getColumns().get(0)); columns.add(foreignColumn); } return columns; } static ArrayList<ForeignColumn> calculateColumns(AccessibleObject field, String fieldName, com.caucho.amber.table.AmberTable mapTable, String prefix, EntityType type, JoinColumn []joinColumnsAnn, HashMap<String, JoinColumnConfig> joinColumnsConfig) throws ConfigException { if ((joinColumnsAnn == null || joinColumnsAnn.length == 0) && (joinColumnsConfig == null || joinColumnsConfig.size() == 0)) return calculateColumns(mapTable, prefix, type); ArrayList<ForeignColumn> columns = new ArrayList<ForeignColumn>(); // #1448 not reproduced. if (type.getId() == null) throw error(field, L.l("Entity {0} has no primary key defined.", type.getName())); ArrayList<IdField> idFields = type.getId().getKeys(); int len; if (joinColumnsAnn != null) len = joinColumnsAnn.length; else len = joinColumnsConfig.size(); if (len != idFields.size()) { throw error(field, L.l("@JoinColumns for {0} do not match number of the primary key columns in {1}. The foreign key columns must match the primary key columns.", fieldName, type.getName())); } Iterator it = null; if (joinColumnsConfig != null) it = joinColumnsConfig.values().iterator(); for (int i = 0; i < len; i++) { ForeignColumn foreignColumn; String name; if (joinColumnsAnn != null) { name = joinColumnsAnn[i].name(); } else { JoinColumnConfig joinColumnConfig = (JoinColumnConfig) it.next(); name = joinColumnConfig.getName(); } foreignColumn = mapTable.createForeignColumn(name, idFields.get(i).getColumns().get(0)); columns.add(foreignColumn); } return columns; } static ConfigException error(AccessibleObject field, String msg) { if (field instanceof Field) return error((Field) field, msg); else return error((Method) field, msg); } static ConfigException error(Field field, String msg) { // XXX: the field is for line numbers in the source, theoretically String className = field.getDeclaringClass().getName(); int line = 0; //field.getLine(); if (line > 0) return new ConfigException(className + ":" + line + ": " + msg); else return new ConfigException(className + "." + field.getName() + ": " + msg); } static ConfigException error(Method field, String msg) { // XXX: the field is for line numbers in the source, theoretically String className = field.getDeclaringClass().getName(); int line = 0; //field.getLine(); if (line > 0) return new ConfigException(className + ":" + line + ": " + msg); else return new ConfigException(className + "." + field.getName() + ": " + msg); } static ArrayList<ForeignColumn> calculateColumns(com.caucho.amber.table.AmberTable mapTable, EntityType type) { ArrayList<ForeignColumn> columns = new ArrayList<ForeignColumn>(); EntityType parentType = type; ArrayList<com.caucho.amber.table.AmberColumn> targetIdColumns; targetIdColumns = type.getId().getColumns(); while (targetIdColumns.size() == 0) { parentType = parentType.getParentType(); if (parentType == null) break; targetIdColumns = parentType.getId().getColumns(); } for (com.caucho.amber.table.AmberColumn key : targetIdColumns) { columns.add(mapTable.createForeignColumn(key.getName(), key)); } return columns; } static ArrayList<ForeignColumn> calculateColumns(com.caucho.amber.table.AmberTable mapTable, String prefix, EntityType type) { ArrayList<ForeignColumn> columns = new ArrayList<ForeignColumn>(); EntityType parentType = type; ArrayList<com.caucho.amber.table.AmberColumn> targetIdColumns; targetIdColumns = type.getId().getColumns(); while (targetIdColumns.size() == 0) { parentType = parentType.getParentType(); if (parentType == null) break; targetIdColumns = parentType.getId().getColumns(); } for (com.caucho.amber.table.AmberColumn key : targetIdColumns) { columns.add(mapTable.createForeignColumn(prefix + key.getName(), key)); } return columns; } protected static String loc(Method method) { return method.getDeclaringClass().getSimpleName() + "." + method.getName() + ": "; } protected static String loc(Field field) { return field.getDeclaringClass().getSimpleName() + "." + field.getName() + ": "; } public static String toSqlName(String name) { return name; // name.toUpperCase(Locale.ENGLISH); } class AnnotationConfig { private Annotation _annotation; private Object _config; public Annotation getAnnotation() { return _annotation; } public Object getConfig() { return _config; } public void setAnnotation(Annotation annotation) { _annotation = annotation; } public void setConfig(Object config) { _config = config; } public boolean isNull() { return (_annotation == null) && (_config == null); } public void reset() { _annotation = null; _config = null; } public void reset(Class type, Class cl) { _annotation = type.getAnnotation(cl); _config = null; } public void reset(AccessibleObject field, Class cl) { _annotation = field.getAnnotation(cl); _config = null; } public EmbeddableConfig getEmbeddableConfig() { return (EmbeddableConfig) _config; } public EntityConfig getEntityConfig() { return (EntityConfig) _config; } public MappedSuperclassConfig getMappedSuperclassConfig() { return (MappedSuperclassConfig) _config; } public EntityListenersConfig getEntityListenersConfig() { return (EntityListenersConfig) _config; } public TableConfig getTableConfig() { return (TableConfig) _config; } public SecondaryTableConfig getSecondaryTableConfig() { return (SecondaryTableConfig) _config; } public IdClassConfig getIdClassConfig() { return (IdClassConfig) _config; } public PostLoadConfig getPostLoadConfig() { return (PostLoadConfig) _config; } public PrePersistConfig getPrePersistConfig() { return (PrePersistConfig) _config; } public PostPersistConfig getPostPersistConfig() { return (PostPersistConfig) _config; } public PreUpdateConfig getPreUpdateConfig() { return (PreUpdateConfig) _config; } public PostUpdateConfig getPostUpdateConfig() { return (PostUpdateConfig) _config; } public PreRemoveConfig getPreRemoveConfig() { return (PreRemoveConfig) _config; } public PostRemoveConfig getPostRemoveConfig() { return (PostRemoveConfig) _config; } public InheritanceConfig getInheritanceConfig() { return (InheritanceConfig) _config; } public NamedQueryConfig getNamedQueryConfig() { return (NamedQueryConfig) _config; } public NamedNativeQueryConfig getNamedNativeQueryConfig() { return (NamedNativeQueryConfig) _config; } public SqlResultSetMappingConfig getSqlResultSetMappingConfig() { return (SqlResultSetMappingConfig) _config; } public PrimaryKeyJoinColumnConfig getPrimaryKeyJoinColumnConfig() { return (PrimaryKeyJoinColumnConfig) _config; } public DiscriminatorColumnConfig getDiscriminatorColumnConfig() { return (DiscriminatorColumnConfig) _config; } public IdConfig getIdConfig() { return (IdConfig) _config; } public EmbeddedIdConfig getEmbeddedIdConfig() { return (EmbeddedIdConfig) _config; } public ColumnConfig getColumnConfig() { return (ColumnConfig) _config; } public GeneratedValueConfig getGeneratedValueConfig() { return (GeneratedValueConfig) _config; } public BasicConfig getBasicConfig() { return (BasicConfig) _config; } public VersionConfig getVersionConfig() { return (VersionConfig) _config; } public ManyToOneConfig getManyToOneConfig() { return (ManyToOneConfig) _config; } public OneToOneConfig getOneToOneConfig() { return (OneToOneConfig) _config; } public ManyToManyConfig getManyToManyConfig() { return (ManyToManyConfig) _config; } public OneToManyConfig getOneToManyConfig() { return (OneToManyConfig) _config; } public MapKeyConfig getMapKeyConfig() { return (MapKeyConfig) _config; } public JoinTableConfig getJoinTableConfig() { return (JoinTableConfig) _config; } public JoinColumnConfig getJoinColumnConfig() { return (JoinColumnConfig) _config; } public AttributeOverrideConfig getAttributeOverrideConfig() { return (AttributeOverrideConfig) _config; } // public AttributeOverridesConfig getAttributeOverridesConfig() { // return (AttributeOverridesConfig) _config; // } public AssociationOverrideConfig getAssociationOverrideConfig() { return (AssociationOverrideConfig) _config; } // public AssociationOverridesConfig getAssociationOverridesConfig() { // return (AssociationOverridesConfig) _config; // } } static { // annotations allowed with a @Basic annotation _basicAnnotations.add("javax.persistence.Basic"); _basicAnnotations.add("javax.persistence.Column"); _basicAnnotations.add("javax.persistence.Enumerated"); _basicAnnotations.add("javax.persistence.Lob"); _basicAnnotations.add("javax.persistence.Temporal"); // non-serializable types allowed with a @Basic annotation _basicTypes.add("boolean"); _basicTypes.add("byte"); _basicTypes.add("char"); _basicTypes.add("short"); _basicTypes.add("int"); _basicTypes.add("long"); _basicTypes.add("float"); _basicTypes.add("double"); _basicTypes.add("[byte"); _basicTypes.add("[char"); _basicTypes.add("[java.lang.Byte"); _basicTypes.add("[java.lang.Character"); // annotations allowed with an @Id annotation _idAnnotations.add("javax.persistence.Column"); _idAnnotations.add("javax.persistence.GeneratedValue"); _idAnnotations.add("javax.persistence.Id"); _idAnnotations.add("javax.persistence.SequenceGenerator"); _idAnnotations.add("javax.persistence.TableGenerator"); _idAnnotations.add("javax.persistence.Temporal"); // allowed with a @Id annotation _idTypes.add("boolean"); _idTypes.add("byte"); _idTypes.add("char"); _idTypes.add("short"); _idTypes.add("int"); _idTypes.add("long"); _idTypes.add("float"); _idTypes.add("double"); _idTypes.add("java.lang.Boolean"); _idTypes.add("java.lang.Byte"); _idTypes.add("java.lang.Character"); _idTypes.add("java.lang.Short"); _idTypes.add("java.lang.Integer"); _idTypes.add("java.lang.Long"); _idTypes.add("java.lang.Float"); _idTypes.add("java.lang.Double"); _idTypes.add("java.lang.String"); _idTypes.add("java.util.Date"); _idTypes.add("java.sql.Date"); // annotations allowed with a @ManyToOne annotation _manyToOneAnnotations.add("javax.persistence.ManyToOne"); _manyToOneAnnotations.add("javax.persistence.JoinColumn"); _manyToOneAnnotations.add("javax.persistence.JoinColumns"); // annotations allowed with a @OneToMany annotation _oneToManyAnnotations.add("javax.persistence.OneToMany"); _oneToManyAnnotations.add("javax.persistence.JoinTable"); _oneToManyAnnotations.add("javax.persistence.MapKey"); _oneToManyAnnotations.add("javax.persistence.OrderBy"); // types allowed with a @OneToMany annotation _oneToManyTypes.add("java.util.Collection"); _oneToManyTypes.add("java.util.List"); _oneToManyTypes.add("java.util.Set"); _oneToManyTypes.add("java.util.Map"); // annotations allowed with a @ManyToMany annotation _manyToManyAnnotations.add("javax.persistence.ManyToMany"); _manyToManyAnnotations.add("javax.persistence.JoinTable"); _manyToManyAnnotations.add("javax.persistence.MapKey"); _manyToManyAnnotations.add("javax.persistence.OrderBy"); // types allowed with a @ManyToMany annotation _manyToManyTypes.add("java.util.Collection"); _manyToManyTypes.add("java.util.List"); _manyToManyTypes.add("java.util.Set"); _manyToManyTypes.add("java.util.Map"); // annotations allowed with a @OneToOne annotation _oneToOneAnnotations.add("javax.persistence.OneToOne"); _oneToOneAnnotations.add("javax.persistence.JoinColumn"); _oneToOneAnnotations.add("javax.persistence.JoinColumns"); // annotations allowed with a @ElementCollection annotation _elementCollectionAnnotations.add("javax.persistence.ElementCollection"); // types allowed with an @ElementCollection annotation _elementCollectionTypes.add("java.util.Collection"); _elementCollectionTypes.add("java.util.List"); _elementCollectionTypes.add("java.util.Set"); _elementCollectionTypes.add("java.util.Map"); // annotations allowed with a @Embedded annotation _embeddedAnnotations.add("javax.persistence.Embedded"); _embeddedAnnotations.add("javax.persistence.AttributeOverride"); _embeddedAnnotations.add("javax.persistence.AttributeOverrides"); _embeddedAnnotations.add("javax.persistence.Column"); // annotations allowed with a @EmbeddedId annotation _embeddedIdAnnotations.add("javax.persistence.EmbeddedId"); _embeddedIdAnnotations.add("javax.persistence.AttributeOverride"); _embeddedIdAnnotations.add("javax.persistence.AttributeOverrides"); // annotations allowed for a property _propertyAnnotations.add("javax.persistence.Basic"); _propertyAnnotations.add("javax.persistence.Column"); _propertyAnnotations.add("javax.persistence.Id"); _propertyAnnotations.add("javax.persistence.Transient"); _propertyAnnotations.add("javax.persistence.OneToOne"); _propertyAnnotations.add("javax.persistence.ManyToOne"); _propertyAnnotations.add("javax.persistence.OneToMany"); _propertyAnnotations.add("javax.persistence.ManyToMany"); _propertyAnnotations.add("javax.persistence.JoinColumn"); _propertyAnnotations.add("javax.persistence.Embedded"); _propertyAnnotations.add("javax.persistence.EmbeddedId"); _propertyAnnotations.add("javax.persistence.Version"); // annotations allowed with a @Version annotation _versionAnnotations.add("javax.persistence.Version"); _versionAnnotations.add("javax.persistence.Column"); _versionAnnotations.add("javax.persistence.Temporal"); // types allowed with a @Version annotation _versionTypes.add("short"); _versionTypes.add("int"); _versionTypes.add("long"); _versionTypes.add("java.lang.Short"); _versionTypes.add("java.lang.Integer"); _versionTypes.add("java.lang.Long"); _versionTypes.add("java.sql.Timestamp"); } }