/* * The contents of this file are subject to the terms * of the Common Development and Distribution License * (the "License"). You may not use this file except * in compliance with the License. * * You can obtain a copy of the license at * glassfish/bootstrap/legal/CDDLv1.0.txt or * https://glassfish.dev.java.net/public/CDDLv1.0.html. * See the License for the specific language governing * permissions and limitations under the License. * * When distributing Covered Code, include this CDDL * HEADER in each file and include the License file at * glassfish/bootstrap/legal/CDDLv1.0.txt. If applicable, * add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your * own identifying information: Portions Copyright [yyyy] * [name of copyright owner] */ // Copyright (c) 1998, 2006, Oracle. All rights reserved. package oracle.toplink.essentials.internal.annotations; import java.util.Hashtable; import java.lang.reflect.AnnotatedElement; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.TableGenerator; import javax.persistence.UniqueConstraint; import javax.persistence.SequenceGenerator; import oracle.toplink.essentials.sequencing.Sequence; import oracle.toplink.essentials.sessions.DatasourceLogin; import oracle.toplink.essentials.sequencing.TableSequence; import oracle.toplink.essentials.sequencing.NativeSequence; import oracle.toplink.essentials.internal.helper.DatabaseTable; import oracle.toplink.essentials.internal.helper.DatabaseField; import oracle.toplink.essentials.exceptions.ValidationException; import oracle.toplink.essentials.internal.ejb.cmp3.metadata.MetadataHelper; /** * SequencingProcessor operates in two phases. * First preProcess methods are called while processing classes and properties. * Those geather the necessary information and perform some validation. * Second step is process method that works on all the gathered information, * does some additional validation and finally performs the actual * sequencing setup on Login and Descriptors. * * Here are the rules enforced by the validation for different Annotations. * * GeneratedValue {GenerationType strategy() default AUTO; String generator() default "";} * AUTO can't have generator: * AUTO implies DEFAULT_AUTO_GENERATOR is used. * SEQUENCE and IDENTITY * without generator use DEFAULT_SEQUENCE_GENERATOR generator; * with generator that is not defined use data defined in DEFAULT_SEQUENCE_GENERATOR generator. * TABLE * without generator use DEFAULT_TABLE_GENERATOR generator; * with generator that is not defined use data defined in DEFAULT_TABLE_GENERATOR generator. * * By defining DEFAULT_AUTO_GENERATOR TableGenerator or SequenceGenerator * the user can define sequence corresponding to AUTO and set as default. * In case DEFAULT_AUTO_GENERATOR generator not defined the * platform-default sequence will be used (TableSequence with all * default settings, see TableSequence class). * * By defining DEFAULT_SEQUENCE_GENERATOR SequenceGenerator * the user can define sequence corresponding to SEQUENCE or ENTITY without generator. * In case DEFAULT_SEQUENCE_GENERATOR generator not defined * NativeSequence with all default settings will be used. * * By defining DEFAULT_TABLE_GENERATOR TableGenerator * the user can define sequence corresponding to TABLE without generator. * In case DEFAULT_TABLE_GENERATOR generator not defined * TableSequence with all default settings will be used. * * SequenceGenerator {String name(); String sequenceName() default ""; int initialValue() default 0; * int allocationSize() default 50;} * sequenceName is defaulted to nothing - that results in name being used; * * TableGenerator {String name();String table() default "";String catalog() default "";String schema() default ""; * String pkColumnName() default "";String valueColumnName() default "";String pkColumnValue() default ""; * int initialValue() default 0;int allocationSize() default 50;UniqueConstraint[] uniqueConstraints() default {};} * table is defaulted to nothing - that results in TableSequence.defaultTableName being used; * pkColumnName is defaulted to nothing - that results in default value of TableSequence.nameFieldName being used; * valueColumnName is defaulted to nothing - that results in default value of TableSequence.counterFieldName being used; * valueColumnValue is defaulted to nothing - that results in name being used; * * Validations catches some of the conflicts: * two objects named the same, but are different; * two tables defined in two TableGenerators named the same but have different fields; * strategy in GeneratedValue contradicts to the type of the generator (e.g. TABLE and SequenceGenerator); * pkColumnValue on a TableGenerator equals sequenceName on a SequenceGenerator; * two sequences with the same name but different preallocation sizes; * two GeneratedValues referencing the same generator with conflicting types (e.g. TABLE and SEQUENCE); * * Note that DEFAULT_AUTO_GENERATOR, DEFAULT_SEQUENCE_GENERATOR and DEFAULT_TABLE_GENERATOR * are String constants defined in this class, not values (so for instance "Generator DEFAULT_AUTO_GENERATOR * is used" means "Generator "SEQ_GEN" is used"). */ public class SequencingProcessor { public static final String DEFAULT_AUTO_GENERATOR = "SEQ_GEN"; public static final String DEFAULT_TABLE_GENERATOR = "SEQ_GEN_TABLE"; public static final String DEFAULT_SEQUENCE_GENERATOR = "SEQ_GEN_SEQUENCE"; // SequenceGenerators keyed on generator name private Hashtable m_sequenceGenerators; // TableGenerators keyed on generator name private Hashtable m_tableGenerators; // Ids keyed on descriptor metadata private Hashtable m_generatedValues; // AnnotatedElements keyed on annotations private Hashtable m_annotatedElements; public SequencingProcessor() { m_tableGenerators = new Hashtable(); m_sequenceGenerators = new Hashtable(); m_generatedValues = new Hashtable(); m_annotatedElements = new Hashtable(); } /** * INTERNAL: * Checks two Id objects for conflicts. Assumes that the objects are not * equal (so may indicate a conflict if obj1==obj2). If conflict is found * then throws conflict exception. */ protected void checkForConflict(GeneratedValue obj1, GeneratedValue obj2) { if (!obj1.generator().equals(obj2.generator())) { return; } else if (obj1.generator().equals("")) { return; } GenerationType type1 = obj1.strategy(); GenerationType type2 = obj2.strategy(); if (type1.equals(type2)) { return; } if( type1.equals(GenerationType.IDENTITY) && type2.equals(GenerationType.SEQUENCE) || type2.equals(GenerationType.IDENTITY) && type1.equals(GenerationType.SEQUENCE)) { return; } throwConflictException(obj1, obj2); } /** * INTERNAL: * Checks two SequenceGenerators objects for conflicts. Assumes that the * objects are not equal (so may indicate a conflict if obj1==obj2). If * conflict is found then throws conflict exception. */ protected void checkForConflict(SequenceGenerator obj1, SequenceGenerator obj2) { // this doesn't even check the initial value?? boolean nameConflict = obj1.name().equals(obj2.name()); boolean sequenceConflict = obj1.sequenceName().equals(obj2.sequenceName()) && obj1.allocationSize() != obj2.allocationSize(); if (nameConflict || sequenceConflict) { throwConflictException(obj1, obj2); } } /** * INTERNAL: * Checks SequenceGenerator, TableGenerator conflicts. * If conflict is found then throws conflict exception. */ protected void checkForConflict(SequenceGenerator obj1, TableGenerator obj2) { checkForConflict(obj2, obj1); } /** * INTERNAL: * Checks TableGenerator, SequenceGenerator conflicts. * If conflict is found then throws conflict exception. */ protected void checkForConflict(TableGenerator obj1, SequenceGenerator obj2) { boolean nameConflict = obj1.name().equals(obj2.name()); boolean sequenceConflict = obj1.pkColumnValue().equals(obj2.sequenceName()); if (nameConflict || sequenceConflict) { throwConflictException(obj1, obj2); } } /** * INTERNAL: * Checks two TableGenerators objects for conflicts. Assumes that the * objects are not equal (so may indicate a conflict if obj1==obj2). * If conflict is found then throws conflict exception. */ protected void checkForConflict(TableGenerator obj1, TableGenerator obj2) { boolean nameConflict = obj1.name().equals(obj2.name()); if (nameConflict) { throwConflictException(obj1, obj2); } boolean tableConflict = obj1.table().equals(obj2.table()) && ( !obj1.pkColumnName().equals(obj2.pkColumnName()) || !obj1.valueColumnName().equals(obj2.valueColumnName())); if (tableConflict) { throwConflictException(obj1, obj2); } boolean sequenceConflict = obj1.pkColumnValue().equals(obj2.pkColumnValue()) && ( obj1.initialValue() != obj2.initialValue() || obj1.allocationSize() != obj2.allocationSize()); if (sequenceConflict) { throwConflictException(obj1, obj2); } } /** * INTERNAL: */ public static String getFullName(AnnotatedElement annotatedElement, Object annotation) { return annotatedElement.toString() + annotation.toString(); } /** * INTERNAL: */ protected String getFullName(Object annotation) { return getFullName(m_annotatedElements.get(annotation), annotation); } /** * INTERNAL: */ public void preProcessGeneratedValue(GeneratedValue generatedValue, Accessor accessor, DatabaseField field, DescriptorMetadata dmd) { AnnotatedElement annotatedElement = accessor.getAnnotatedElement(); // If there is a GeneratedValue then process it, otherwise, do nothing. // Should never be true if we are dealing with a composite primary key. if (generatedValue != null) { m_annotatedElements.put(generatedValue, annotatedElement); String generatorName = generatedValue.generator(); GenerationType generatorType = generatedValue.strategy(); if (generatorType == GenerationType.AUTO && ! generatorName.equals("")) { throw ValidationException.idAnnotationCannotSpecifyGenerator(getFullName(annotatedElement, generatedValue), generatorType.toString()); } // Set the sequence number field on the descriptor. dmd.setSequenceNumberField(field); for (GeneratedValue other : m_generatedValues.values()) { checkForConflict(other, generatedValue); } m_generatedValues.put(dmd, generatedValue); } } /** * INTERNAL: */ public void preProcessSequenceGenerator(SequenceGenerator sequenceGenerator, AnnotatedElement annotatedElement) { if (sequenceGenerator != null) { m_annotatedElements.put(sequenceGenerator, annotatedElement); String generatorName = sequenceGenerator.name(); // DEFAULT_TABLE_GENERATOR can't be used as SequenceGenerator name if (sequenceGenerator.equals(DEFAULT_TABLE_GENERATOR)) { throw ValidationException.reservedName(getFullName(annotatedElement, sequenceGenerator), DEFAULT_TABLE_GENERATOR, TableGenerator.class.getName()); } SequenceGenerator otherSequenceGenerator = m_sequenceGenerators.get(generatorName); if (otherSequenceGenerator != null) { if (otherSequenceGenerator.equals(sequenceGenerator)) { return; // duplication } } for (SequenceGenerator other : m_sequenceGenerators.values()) { checkForConflict(other, sequenceGenerator); } for (TableGenerator other : m_tableGenerators.values()) { checkForConflict(other, sequenceGenerator); } m_sequenceGenerators.put(generatorName, sequenceGenerator); } } /** * INTERNAL: */ public void preProcessTableGenerator(TableGenerator tableGenerator, AnnotatedElement annotatedElement) { if (tableGenerator != null) { m_annotatedElements.put(tableGenerator, annotatedElement); String generatorName = tableGenerator.name(); // DEFAULT_SEQUENCE_GENERATOR can't be used as TableGenerator name if (generatorName.equals(DEFAULT_SEQUENCE_GENERATOR)) { throw ValidationException.reservedName(getFullName(annotatedElement, tableGenerator), DEFAULT_SEQUENCE_GENERATOR, SequenceGenerator.class.getName()); } TableGenerator otherTableGenerator = m_tableGenerators.get(generatorName); if (otherTableGenerator != null) { if (otherTableGenerator.equals(tableGenerator)) { return; // duplication } } for (TableGenerator other : m_tableGenerators.values()) { checkForConflict(other, tableGenerator); } for (SequenceGenerator other : m_sequenceGenerators.values()) { checkForConflict(other, tableGenerator); } m_tableGenerators.put(generatorName, tableGenerator); } } /** * INTERNAL: * Performs sequencing setup on Login and Descriptors */ public void process(DatasourceLogin login) { if (m_generatedValues.isEmpty()) { return; // sequence is not used } // Generators referenced from Id should have correct type for (GeneratedValue generatedValue : m_generatedValues.values()) { GenerationType type = generatedValue.strategy(); String generatorName = generatedValue.generator(); if (type.equals(GenerationType.TABLE)) { SequenceGenerator sequenceGenerator = m_sequenceGenerators.get(generatorName); if (sequenceGenerator != null) { throwConflictException(generatedValue, sequenceGenerator); } } else if (type.equals(GenerationType.SEQUENCE) || type.equals(GenerationType.IDENTITY)) { TableGenerator tableGenerator = m_tableGenerators.get(generatorName); if (tableGenerator != null) { throwConflictException(generatedValue, tableGenerator); } } } Sequence defaultAutoSequence = null; NativeSequence defaultNativeSequence = new NativeSequence(DEFAULT_SEQUENCE_GENERATOR); TableSequence defaultTableSequence = new TableSequence(DEFAULT_TABLE_GENERATOR); // Sequences keyed on generator names. Hashtable sequences = new Hashtable(); //String seqName; for (SequenceGenerator sequenceGenerator : m_sequenceGenerators.values()) { String sequenceGeneratorName = sequenceGenerator.name(); String seqName = (sequenceGenerator.sequenceName().equals("")) ? sequenceGeneratorName : sequenceGenerator.sequenceName(); NativeSequence sequence = new NativeSequence(seqName, sequenceGenerator.allocationSize()); sequences.put(sequenceGeneratorName, sequence); if (sequenceGeneratorName.equals(DEFAULT_AUTO_GENERATOR)) { // SequenceGenerator defined with DEFAULT_AUTO_GENERATOR. // The sequence it defines will be used as a defaultSequence. defaultAutoSequence = sequence; } else if (sequenceGeneratorName.equals(DEFAULT_SEQUENCE_GENERATOR)) { // SequenceGenerator deinfed with DEFAULT_SEQUENCE_GENERATOR. // All sequences of GeneratorType SEQUENCE and IDENTITY // referencing non-defined generators will use a clone of the // sequence defined by this generator. defaultNativeSequence = sequence; } } for (TableGenerator tableGenerator : m_tableGenerators.values()) { String tableGeneratorName = tableGenerator.name(); String seqName = (tableGenerator.pkColumnValue().equals("")) ? tableGeneratorName : tableGenerator.pkColumnValue(); TableSequence sequence = new TableSequence(seqName, tableGenerator.allocationSize()); sequences.put(tableGeneratorName, sequence); // Get the database table from the @TableGenerator values. // In case tableGenerator.table().equals("") default sequence table name will be extracted from sequence and used, // see TableSequence class. sequence.setTable(new DatabaseTable(MetadataHelper.getFullyQualifiedTableName(tableGenerator.table(), sequence.getTableName(), tableGenerator.catalog(), tableGenerator.schema()))); // Process the @UniqueConstraints for this table. for (UniqueConstraint uniqueConstraint : tableGenerator.uniqueConstraints()) { // UniqueConstraint.columnNames() String[] columnNames = uniqueConstraint.columnNames(); sequence.getTable().addUniqueConstraint(columnNames); } if (!tableGenerator.pkColumnName().equals("")) { sequence.setNameFieldName(tableGenerator.pkColumnName()); } if (!tableGenerator.valueColumnName().equals("")) { sequence.setCounterFieldName(tableGenerator.valueColumnName()); } if (tableGeneratorName.equals(DEFAULT_AUTO_GENERATOR)) { // TableGenerator defined with DEFAULT_AUTO_GENERATOR. // The sequence it defines will be used as a defaultSequence. defaultAutoSequence = sequence; } else if (tableGeneratorName.equals(DEFAULT_TABLE_GENERATOR)) { // SequenceGenerator defined with DEFAULT_TABLE_GENERATOR. All // sequences of GenerationType TABLE referencing non-defined // generators will use a clone of the sequence defined by this // generator. defaultTableSequence = sequence; } } // Finally loop through descriptors and set sequences as required into // Descriptors and Login for (DescriptorMetadata dmd : m_generatedValues.keySet()) { GeneratedValue generatedValue = m_generatedValues.get(dmd); String generatorName = generatedValue.generator(); Sequence sequence = null; if (!generatorName.equals("")) { sequence = sequences.get(generatorName); } if (sequence == null) { if (generatedValue.strategy() == GenerationType.TABLE) { if (generatorName.equals("")) { sequence = defaultTableSequence; } else { sequence = (Sequence)defaultTableSequence.clone(); sequence.setName(generatorName); } } else if (generatedValue.strategy() == GenerationType.SEQUENCE || generatedValue.strategy() == GenerationType.IDENTITY) { if (generatorName.equals("")) { sequence = defaultNativeSequence; } else { sequence = (Sequence)defaultNativeSequence.clone(); sequence.setName(generatorName); } } } if (sequence != null) { dmd.setSequenceNumberName(sequence.getName()); login.addSequence(sequence); } else if (generatedValue.strategy() == GenerationType.AUTO) { if (defaultAutoSequence != null) { dmd.setSequenceNumberName(defaultAutoSequence.getName()); login.setDefaultSequence(defaultAutoSequence); } else { dmd.setSequenceNumberName(DEFAULT_AUTO_GENERATOR); } } else { assert false; // should never happen } } } /** * INTERNAL: * Throws an exception indicating a conflict between two objects */ protected void throwConflictException(Object obj1, Object obj2) { throw ValidationException.annotationsConflict(getFullName(obj1), getFullName(obj2)); } }