/** @author Steve Ebersole */ public class ResourceRegistryStandardImpl implements ResourceRegistry { private static final CoreMessageLogger log = CoreLogging.messageLogger(ResourceRegistryStandardImpl.class); private final Map<Statement, Set<ResultSet>> xref = new HashMap<Statement, Set<ResultSet>>(); private final Set<ResultSet> unassociatedResultSets = new HashSet<ResultSet>(); private List<Blob> blobs; private List<Clob> clobs; private List<NClob> nclobs; private Statement lastQuery; @Override public boolean hasRegisteredResources() { return hasRegistered(xref) || hasRegistered(unassociatedResultSets) || hasRegistered(blobs) || hasRegistered(clobs) || hasRegistered(nclobs); } @Override public void register(Statement statement, boolean cancelable) { log.tracef("Registering statement [%s]", statement); if (xref.containsKey(statement)) { throw new HibernateException("JDBC Statement already registered"); } xref.put(statement, null); if (cancelable) { lastQuery = statement; } } @Override public void release(Statement statement) { log.tracev("Releasing statement [{0}]", statement); // Keep this at DEBUG level, rather than warn. Numerous connection pool implementations can // return a // proxy/wrapper around the JDBC Statement, causing excessive logging here. See HHH-8210. if (log.isDebugEnabled() && !xref.containsKey(statement)) { log.unregisteredStatement(); } else { final Set<ResultSet> resultSets = xref.get(statement); if (resultSets != null) { closeAll(resultSets); } xref.remove(statement); } close(statement); if (lastQuery == statement) { lastQuery = null; } } @Override public void release(ResultSet resultSet, Statement statement) { log.tracef("Releasing result set [%s]", resultSet); if (statement == null) { try { statement = resultSet.getStatement(); } catch (SQLException e) { throw convert(e, "unable to access Statement from ResultSet"); } } if (statement != null) { final Set<ResultSet> resultSets = xref.get(statement); if (resultSets == null) { log.unregisteredStatement(); } else { resultSets.remove(resultSet); if (resultSets.isEmpty()) { xref.remove(statement); } } } else { final boolean removed = unassociatedResultSets.remove(resultSet); if (!removed) { log.unregisteredResultSetWithoutStatement(); } } close(resultSet); } protected void closeAll(Set<ResultSet> resultSets) { for (ResultSet resultSet : resultSets) { close(resultSet); } resultSets.clear(); } @SuppressWarnings({"unchecked"}) public static void close(ResultSet resultSet) { log.tracef("Closing result set [%s]", resultSet); try { resultSet.close(); } catch (SQLException e) { log.debugf("Unable to release JDBC result set [%s]", e.getMessage()); } catch (Exception e) { // try to handle general errors more elegantly log.debugf("Unable to release JDBC result set [%s]", e.getMessage()); } } @SuppressWarnings({"unchecked"}) public static void close(Statement statement) { log.tracef("Closing prepared statement [%s]", statement); try { // if we are unable to "clean" the prepared statement, // we do not close it try { if (statement.getMaxRows() != 0) { statement.setMaxRows(0); } if (statement.getQueryTimeout() != 0) { statement.setQueryTimeout(0); } } catch (SQLException sqle) { // there was a problem "cleaning" the prepared statement if (log.isDebugEnabled()) { log.debugf("Exception clearing maxRows/queryTimeout [%s]", sqle.getMessage()); } // EARLY EXIT!!! return; } statement.close(); } catch (SQLException e) { log.debugf("Unable to release JDBC statement [%s]", e.getMessage()); } catch (Exception e) { // try to handle general errors more elegantly log.debugf("Unable to release JDBC statement [%s]", e.getMessage()); } } @Override public void register(ResultSet resultSet, Statement statement) { log.tracef("Registering result set [%s]", resultSet); if (statement == null) { try { statement = resultSet.getStatement(); } catch (SQLException e) { throw convert(e, "unable to access Statement from ResultSet"); } } if (statement != null) { // Keep this at DEBUG level, rather than warn. Numerous connection pool implementations can // return a // proxy/wrapper around the JDBC Statement, causing excessive logging here. See HHH-8210. if (log.isDebugEnabled() && !xref.containsKey(statement)) { log.debug("ResultSet statement was not registered (on register)"); } Set<ResultSet> resultSets = xref.get(statement); if (resultSets == null) { resultSets = new HashSet<ResultSet>(); xref.put(statement, resultSets); } resultSets.add(resultSet); } else { unassociatedResultSets.add(resultSet); } } private JDBCException convert(SQLException e, String s) { // todo : implement return null; } @Override public void register(Blob blob) { if (blobs == null) { blobs = new ArrayList<Blob>(); } blobs.add(blob); } @Override public void release(Blob blob) { if (blobs == null) { log.debug("Request to release Blob, but appears no Blobs have ever been registered"); return; } blobs.remove(blob); } @Override public void register(Clob clob) { if (clobs == null) { clobs = new ArrayList<Clob>(); } clobs.add(clob); } @Override public void release(Clob clob) { if (clobs == null) { log.debug("Request to release Clob, but appears no Clobs have ever been registered"); return; } clobs.remove(clob); } @Override public void register(NClob nclob) { // todo : just store them in clobs? if (nclobs == null) { nclobs = new ArrayList<NClob>(); } nclobs.add(nclob); } @Override public void release(NClob nclob) { // todo : just store them in clobs? if (nclobs == null) { log.debug("Request to release NClob, but appears no NClobs have ever been registered"); return; } nclobs.remove(nclob); } @Override public void cancelLastQuery() { try { if (lastQuery != null) { lastQuery.cancel(); } } catch (SQLException e) { throw convert(e, "Cannot cancel query"); } finally { lastQuery = null; } } @Override public void releaseResources() { log.trace("Releasing JDBC resources"); for (Map.Entry<Statement, Set<ResultSet>> entry : xref.entrySet()) { if (entry.getValue() != null) { closeAll(entry.getValue()); } close(entry.getKey()); } xref.clear(); closeAll(unassociatedResultSets); if (blobs != null) { for (Blob blob : blobs) { try { blob.free(); } catch (SQLException e) { log.debugf("Unable to free JDBC Blob reference [%s]", e.getMessage()); } } blobs.clear(); } if (clobs != null) { for (Clob clob : clobs) { try { clob.free(); } catch (SQLException e) { log.debugf("Unable to free JDBC Clob reference [%s]", e.getMessage()); } } clobs.clear(); } if (nclobs != null) { for (NClob nclob : nclobs) { try { nclob.free(); } catch (SQLException e) { log.debugf("Unable to free JDBC NClob reference [%s]", e.getMessage()); } } nclobs.clear(); } } private boolean hasRegistered(Map resource) { return resource != null && !resource.isEmpty(); } private boolean hasRegistered(Collection resource) { return resource != null && !resource.isEmpty(); } }
/** * An abstract {@link CollectionInitializer} implementation based on using LoadPlans * * @author Gail Badner */ public abstract class AbstractLoadPlanBasedCollectionInitializer extends AbstractLoadPlanBasedLoader implements CollectionInitializer { private static final CoreMessageLogger log = CoreLogging.messageLogger(AbstractLoadPlanBasedCollectionInitializer.class); private final QueryableCollection collectionPersister; private final LoadQueryDetails staticLoadQuery; public AbstractLoadPlanBasedCollectionInitializer( QueryableCollection collectionPersister, QueryBuildingParameters buildingParameters) { super(collectionPersister.getFactory()); this.collectionPersister = collectionPersister; final FetchStyleLoadPlanBuildingAssociationVisitationStrategy strategy = new FetchStyleLoadPlanBuildingAssociationVisitationStrategy( collectionPersister.getFactory(), buildingParameters.getQueryInfluencers(), buildingParameters.getLockMode() != null ? buildingParameters.getLockMode() : buildingParameters.getLockOptions().getLockMode()); final LoadPlan plan = MetamodelDrivenLoadPlanBuilder.buildRootCollectionLoadPlan(strategy, collectionPersister); this.staticLoadQuery = BatchingLoadQueryDetailsFactory.INSTANCE.makeCollectionLoadQueryDetails( collectionPersister, plan, buildingParameters); } @Override public void initialize(Serializable id, SessionImplementor session) throws HibernateException { if (log.isDebugEnabled()) { log.debugf( "Loading collection: %s", MessageHelper.collectionInfoString(collectionPersister, id, getFactory())); } final Serializable[] ids = new Serializable[] {id}; try { final QueryParameters qp = new QueryParameters(); qp.setPositionalParameterTypes(new Type[] {collectionPersister.getKeyType()}); qp.setPositionalParameterValues(ids); qp.setCollectionKeys(ids); executeLoad(session, qp, staticLoadQuery, true, null); } catch (SQLException sqle) { throw getFactory() .getSQLExceptionHelper() .convert( sqle, "could not initialize a collection: " + MessageHelper.collectionInfoString(collectionPersister, id, getFactory()), staticLoadQuery.getSqlStatement()); } log.debug("Done loading collection"); } protected QueryableCollection collectionPersister() { return collectionPersister; } @Override protected LoadQueryDetails getStaticLoadQuery() { return staticLoadQuery; } @Override protected int[] getNamedParameterLocs(String name) { throw new AssertionFailure("no named parameters"); } @Override protected void autoDiscoverTypes(ResultSet rs) { throw new AssertionFailure("Auto discover types not supported in this loader"); } }
/** * Any value that maps to columns. * * @author Gavin King */ public class SimpleValue implements KeyValue { private static final CoreMessageLogger log = CoreLogging.messageLogger(SimpleValue.class); public static final String DEFAULT_ID_GEN_STRATEGY = "assigned"; private final MetadataImplementor metadata; private final List<Selectable> columns = new ArrayList<Selectable>(); private String typeName; private Properties typeParameters; private boolean isNationalized; private Properties identifierGeneratorProperties; private String identifierGeneratorStrategy = DEFAULT_ID_GEN_STRATEGY; private String nullValue; private Table table; private String foreignKeyName; private boolean alternateUniqueKey; private boolean cascadeDeleteEnabled; private AttributeConverterDefinition attributeConverterDefinition; private Type type; public SimpleValue(MetadataImplementor metadata) { this.metadata = metadata; } public SimpleValue(MetadataImplementor metadata, Table table) { this(metadata); this.table = table; } public MetadataImplementor getMetadata() { return metadata; } @Override public ServiceRegistry getServiceRegistry() { return getMetadata().getMetadataBuildingOptions().getServiceRegistry(); } @Override public boolean isCascadeDeleteEnabled() { return cascadeDeleteEnabled; } public void setCascadeDeleteEnabled(boolean cascadeDeleteEnabled) { this.cascadeDeleteEnabled = cascadeDeleteEnabled; } public void addColumn(Column column) { if (!columns.contains(column)) { columns.add(column); } column.setValue(this); column.setTypeIndex(columns.size() - 1); } public void addFormula(Formula formula) { columns.add(formula); } @Override public boolean hasFormula() { Iterator iter = getColumnIterator(); while (iter.hasNext()) { Object o = iter.next(); if (o instanceof Formula) { return true; } } return false; } @Override public int getColumnSpan() { return columns.size(); } @Override public Iterator<Selectable> getColumnIterator() { return columns.iterator(); } public List getConstraintColumns() { return columns; } public String getTypeName() { return typeName; } public void setTypeName(String typeName) { if (typeName != null && typeName.startsWith(AttributeConverterTypeAdapter.NAME_PREFIX)) { final String converterClassName = typeName.substring(AttributeConverterTypeAdapter.NAME_PREFIX.length()); final ClassLoaderService cls = getMetadata() .getMetadataBuildingOptions() .getServiceRegistry() .getService(ClassLoaderService.class); try { final Class<AttributeConverter> converterClass = cls.classForName(converterClassName); attributeConverterDefinition = new AttributeConverterDefinition(converterClass.newInstance(), false); return; } catch (Exception e) { log.logBadHbmAttributeConverterType(typeName, e.getMessage()); } } this.typeName = typeName; } public void makeNationalized() { this.isNationalized = true; } public boolean isNationalized() { return isNationalized; } public void setTable(Table table) { this.table = table; } @Override public void createForeignKey() throws MappingException {} @Override public void createForeignKeyOfEntity(String entityName) { if (!hasFormula() && !"none".equals(getForeignKeyName())) { ForeignKey fk = table.createForeignKey(getForeignKeyName(), getConstraintColumns(), entityName); fk.setCascadeDeleteEnabled(cascadeDeleteEnabled); } } private IdentifierGenerator identifierGenerator; @Override public IdentifierGenerator createIdentifierGenerator( IdentifierGeneratorFactory identifierGeneratorFactory, Dialect dialect, String defaultCatalog, String defaultSchema, RootClass rootClass) throws MappingException { if (identifierGenerator != null) { return identifierGenerator; } Properties params = new Properties(); // if the hibernate-mapping did not specify a schema/catalog, use the defaults // specified by properties - but note that if the schema/catalog were specified // in hibernate-mapping, or as params, they will already be initialized and // will override the values set here (they are in identifierGeneratorProperties) if (defaultSchema != null) { params.setProperty(PersistentIdentifierGenerator.SCHEMA, defaultSchema); } if (defaultCatalog != null) { params.setProperty(PersistentIdentifierGenerator.CATALOG, defaultCatalog); } // pass the entity-name, if not a collection-id if (rootClass != null) { params.setProperty(IdentifierGenerator.ENTITY_NAME, rootClass.getEntityName()); params.setProperty(IdentifierGenerator.JPA_ENTITY_NAME, rootClass.getJpaEntityName()); } // init the table here instead of earlier, so that we can get a quoted table name // TODO: would it be better to simply pass the qualified table name, instead of // splitting it up into schema/catalog/table names String tableName = getTable().getQuotedName(dialect); params.setProperty(PersistentIdentifierGenerator.TABLE, tableName); // pass the column name (a generated id almost always has a single column) String columnName = ((Column) getColumnIterator().next()).getQuotedName(dialect); params.setProperty(PersistentIdentifierGenerator.PK, columnName); if (rootClass != null) { StringBuilder tables = new StringBuilder(); Iterator iter = rootClass.getIdentityTables().iterator(); while (iter.hasNext()) { Table table = (Table) iter.next(); tables.append(table.getQuotedName(dialect)); if (iter.hasNext()) { tables.append(", "); } } params.setProperty(PersistentIdentifierGenerator.TABLES, tables.toString()); } else { params.setProperty(PersistentIdentifierGenerator.TABLES, tableName); } if (identifierGeneratorProperties != null) { params.putAll(identifierGeneratorProperties); } // TODO : we should pass along all settings once "config lifecycle" is hashed out... final ConfigurationService cs = metadata .getMetadataBuildingOptions() .getServiceRegistry() .getService(ConfigurationService.class); params.put( AvailableSettings.PREFER_POOLED_VALUES_LO, cs.getSetting( AvailableSettings.PREFER_POOLED_VALUES_LO, StandardConverters.BOOLEAN, false)); if (cs.getSettings().get(AvailableSettings.PREFERRED_POOLED_OPTIMIZER) != null) { params.put( AvailableSettings.PREFERRED_POOLED_OPTIMIZER, cs.getSettings().get(AvailableSettings.PREFERRED_POOLED_OPTIMIZER)); } identifierGeneratorFactory.setDialect(dialect); identifierGenerator = identifierGeneratorFactory.createIdentifierGenerator( identifierGeneratorStrategy, getType(), params); return identifierGenerator; } public boolean isUpdateable() { // needed to satisfy KeyValue return true; } public FetchMode getFetchMode() { return FetchMode.SELECT; } public Properties getIdentifierGeneratorProperties() { return identifierGeneratorProperties; } public String getNullValue() { return nullValue; } public Table getTable() { return table; } /** * Returns the identifierGeneratorStrategy. * * @return String */ public String getIdentifierGeneratorStrategy() { return identifierGeneratorStrategy; } public boolean isIdentityColumn( IdentifierGeneratorFactory identifierGeneratorFactory, Dialect dialect) { identifierGeneratorFactory.setDialect(dialect); return identifierGeneratorFactory .getIdentifierGeneratorClass(identifierGeneratorStrategy) .equals(IdentityGenerator.class); } /** * Sets the identifierGeneratorProperties. * * @param identifierGeneratorProperties The identifierGeneratorProperties to set */ public void setIdentifierGeneratorProperties(Properties identifierGeneratorProperties) { this.identifierGeneratorProperties = identifierGeneratorProperties; } /** * Sets the identifierGeneratorStrategy. * * @param identifierGeneratorStrategy The identifierGeneratorStrategy to set */ public void setIdentifierGeneratorStrategy(String identifierGeneratorStrategy) { this.identifierGeneratorStrategy = identifierGeneratorStrategy; } /** * Sets the nullValue. * * @param nullValue The nullValue to set */ public void setNullValue(String nullValue) { this.nullValue = nullValue; } public String getForeignKeyName() { return foreignKeyName; } public void setForeignKeyName(String foreignKeyName) { this.foreignKeyName = foreignKeyName; } public boolean isAlternateUniqueKey() { return alternateUniqueKey; } public void setAlternateUniqueKey(boolean unique) { this.alternateUniqueKey = unique; } public boolean isNullable() { Iterator itr = getColumnIterator(); while (itr.hasNext()) { final Object selectable = itr.next(); if (selectable instanceof Formula) { // if there are *any* formulas, then the Value overall is // considered nullable return true; } else if (!((Column) selectable).isNullable()) { // if there is a single non-nullable column, the Value // overall is considered non-nullable. return false; } } // nullable by default return true; } public boolean isSimpleValue() { return true; } public boolean isValid(Mapping mapping) throws MappingException { return getColumnSpan() == getType().getColumnSpan(mapping); } public Type getType() throws MappingException { if (type != null) { return type; } if (typeName == null) { throw new MappingException("No type name"); } if (typeParameters != null && Boolean.valueOf(typeParameters.getProperty(DynamicParameterizedType.IS_DYNAMIC)) && typeParameters.get(DynamicParameterizedType.PARAMETER_TYPE) == null) { createParameterImpl(); } Type result = metadata.getTypeResolver().heuristicType(typeName, typeParameters); if (result == null) { String msg = "Could not determine type for: " + typeName; if (table != null) { msg += ", at table: " + table.getName(); } if (columns != null && columns.size() > 0) { msg += ", for columns: " + columns; } throw new MappingException(msg); } return result; } public void setTypeUsingReflection(String className, String propertyName) throws MappingException { // NOTE : this is called as the last piece in setting SimpleValue type information, and // implementations // rely on that fact, using it as a signal that all information it is going to get is defined at // this point... if (typeName != null) { // assume either (a) explicit type was specified or (b) determine was already performed return; } if (type != null) { return; } if (attributeConverterDefinition == null) { // this is here to work like legacy. This should change when we integrate with metamodel to // look for SqlTypeDescriptor and JavaTypeDescriptor individually and create the BasicType // (well, really // keep a registry of [SqlTypeDescriptor,JavaTypeDescriptor] -> BasicType...) if (className == null) { throw new MappingException( "Attribute types for a dynamic entity must be explicitly specified: " + propertyName); } typeName = ReflectHelper.reflectedPropertyClass( className, propertyName, metadata .getMetadataBuildingOptions() .getServiceRegistry() .getService(ClassLoaderService.class)) .getName(); // todo : to fully support isNationalized here we need do the process hinted at above // essentially, much of the logic from #buildAttributeConverterTypeAdapter wrt resolving // a (1) SqlTypeDescriptor, a (2) JavaTypeDescriptor and dynamically building a BasicType // combining them. return; } // we had an AttributeConverter... type = buildAttributeConverterTypeAdapter(); } /** * Build a Hibernate Type that incorporates the JPA AttributeConverter. AttributeConverter works * totally in memory, meaning it converts between one Java representation (the entity attribute * representation) and another (the value bound into JDBC statements or extracted from results). * However, the Hibernate Type system operates at the lower level of actually dealing directly * with those JDBC objects. So even though we have an AttributeConverter, we still need to "fill * out" the rest of the BasicType data and bridge calls to bind/extract through the converter. * * <p>Essentially the idea here is that an intermediate Java type needs to be used. Let's use an * example as a means to illustrate... Consider an {@code AttributeConverter<Integer,String>}. * This tells Hibernate that the domain model defines this attribute as an Integer value (the * 'entityAttributeJavaType'), but that we need to treat the value as a String (the * 'databaseColumnJavaType') when dealing with JDBC (aka, the database type is a VARCHAR/CHAR): * * <ul> * <li>When binding values to PreparedStatements we need to convert the Integer value from the * entity into a String and pass that String to setString. The conversion is handled by * calling {@link AttributeConverter#convertToDatabaseColumn(Object)} * <li>When extracting values from ResultSets (or CallableStatement parameters) we need to * handle the value via getString, and convert that returned String to an Integer. That * conversion is handled by calling {@link * AttributeConverter#convertToEntityAttribute(Object)} * </ul> * * @return The built AttributeConverter -> Type adapter * @todo : ultimately I want to see attributeConverterJavaType and attributeConverterJdbcTypeCode * specify-able separately then we can "play them against each other" in terms of determining * proper typing * @todo : see if we already have previously built a custom on-the-fly BasicType for this * AttributeConverter; see note below about caching */ @SuppressWarnings("unchecked") private Type buildAttributeConverterTypeAdapter() { // todo : validate the number of columns present here? final Class entityAttributeJavaType = attributeConverterDefinition.getEntityAttributeType(); final Class databaseColumnJavaType = attributeConverterDefinition.getDatabaseColumnType(); // resolve the JavaTypeDescriptor // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // For the JavaTypeDescriptor portion we simply resolve the "entity attribute representation" // part of // the AttributeConverter to resolve the corresponding descriptor. final JavaTypeDescriptor entityAttributeJavaTypeDescriptor = JavaTypeDescriptorRegistry.INSTANCE.getDescriptor(entityAttributeJavaType); // build the SqlTypeDescriptor adapter // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Going back to the illustration, this should be a SqlTypeDescriptor that handles the Integer // <-> String // conversions. This is the more complicated piece. First we need to determine the JDBC type // code // corresponding to the AttributeConverter's declared "databaseColumnJavaType" (how we read // that value out // of ResultSets). See JdbcTypeJavaClassMappings for details. Again, given example, this // should return // VARCHAR/CHAR int jdbcTypeCode = JdbcTypeJavaClassMappings.INSTANCE.determineJdbcTypeCodeForJavaClass( databaseColumnJavaType); if (isNationalized()) { jdbcTypeCode = NationalizedTypeMappings.INSTANCE.getCorrespondingNationalizedCode(jdbcTypeCode); } // find the standard SqlTypeDescriptor for that JDBC type code. final SqlTypeDescriptor sqlTypeDescriptor = SqlTypeDescriptorRegistry.INSTANCE.getDescriptor(jdbcTypeCode); // find the JavaTypeDescriptor representing the "intermediate database type representation". // Back to the // illustration, this should be the type descriptor for Strings final JavaTypeDescriptor intermediateJavaTypeDescriptor = JavaTypeDescriptorRegistry.INSTANCE.getDescriptor(databaseColumnJavaType); // and finally construct the adapter, which injects the AttributeConverter calls into the // binding/extraction // process... final SqlTypeDescriptor sqlTypeDescriptorAdapter = new AttributeConverterSqlTypeDescriptorAdapter( attributeConverterDefinition.getAttributeConverter(), sqlTypeDescriptor, intermediateJavaTypeDescriptor); // todo : cache the AttributeConverterTypeAdapter in case that AttributeConverter is applied // multiple times. final String name = AttributeConverterTypeAdapter.NAME_PREFIX + attributeConverterDefinition.getAttributeConverter().getClass().getName(); final String description = String.format( "BasicType adapter for AttributeConverter<%s,%s>", entityAttributeJavaType.getSimpleName(), databaseColumnJavaType.getSimpleName()); return new AttributeConverterTypeAdapter( name, description, attributeConverterDefinition.getAttributeConverter(), sqlTypeDescriptorAdapter, entityAttributeJavaType, databaseColumnJavaType, entityAttributeJavaTypeDescriptor); } public boolean isTypeSpecified() { return typeName != null; } public void setTypeParameters(Properties parameterMap) { this.typeParameters = parameterMap; } public Properties getTypeParameters() { return typeParameters; } public void copyTypeFrom(SimpleValue sourceValue) { setTypeName(sourceValue.getTypeName()); setTypeParameters(sourceValue.getTypeParameters()); type = sourceValue.type; attributeConverterDefinition = sourceValue.attributeConverterDefinition; } @Override public String toString() { return getClass().getName() + '(' + columns.toString() + ')'; } public Object accept(ValueVisitor visitor) { return visitor.accept(this); } public boolean[] getColumnInsertability() { boolean[] result = new boolean[getColumnSpan()]; int i = 0; Iterator iter = getColumnIterator(); while (iter.hasNext()) { Selectable s = (Selectable) iter.next(); result[i++] = !s.isFormula(); } return result; } public boolean[] getColumnUpdateability() { return getColumnInsertability(); } public void setJpaAttributeConverterDefinition( AttributeConverterDefinition attributeConverterDefinition) { this.attributeConverterDefinition = attributeConverterDefinition; } private void createParameterImpl() { try { String[] columnsNames = new String[columns.size()]; for (int i = 0; i < columns.size(); i++) { Selectable column = columns.get(i); if (column instanceof Column) { columnsNames[i] = ((Column) column).getName(); } } final XProperty xProperty = (XProperty) typeParameters.get(DynamicParameterizedType.XPROPERTY); // todo : not sure this works for handling @MapKeyEnumerated final Annotation[] annotations = xProperty == null ? null : xProperty.getAnnotations(); final ClassLoaderService classLoaderService = getMetadata() .getMetadataBuildingOptions() .getServiceRegistry() .getService(ClassLoaderService.class); typeParameters.put( DynamicParameterizedType.PARAMETER_TYPE, new ParameterTypeImpl( classLoaderService.classForName( typeParameters.getProperty(DynamicParameterizedType.RETURNED_CLASS)), annotations, table.getCatalog(), table.getSchema(), table.getName(), Boolean.valueOf(typeParameters.getProperty(DynamicParameterizedType.IS_PRIMARY_KEY)), columnsNames)); } catch (ClassLoadingException e) { throw new MappingException( "Could not create DynamicParameterizedType for type: " + typeName, e); } } private static final class ParameterTypeImpl implements DynamicParameterizedType.ParameterType { private final Class returnedClass; private final Annotation[] annotationsMethod; private final String catalog; private final String schema; private final String table; private final boolean primaryKey; private final String[] columns; private ParameterTypeImpl( Class returnedClass, Annotation[] annotationsMethod, String catalog, String schema, String table, boolean primaryKey, String[] columns) { this.returnedClass = returnedClass; this.annotationsMethod = annotationsMethod; this.catalog = catalog; this.schema = schema; this.table = table; this.primaryKey = primaryKey; this.columns = columns; } @Override public Class getReturnedClass() { return returnedClass; } @Override public Annotation[] getAnnotationsMethod() { return annotationsMethod; } @Override public String getCatalog() { return catalog; } @Override public String getSchema() { return schema; } @Override public String getTable() { return table; } @Override public boolean isPrimaryKey() { return primaryKey; } @Override public String[] getColumns() { return columns; } } }
/** * Defines the default delete event listener used by hibernate for deleting entities from the * datastore in response to generated delete events. * * @author Steve Ebersole */ public class DefaultDeleteEventListener implements DeleteEventListener { private static final CoreMessageLogger LOG = CoreLogging.messageLogger(DefaultDeleteEventListener.class); /** * Handle the given delete event. * * @param event The delete event to be handled. * @throws HibernateException */ public void onDelete(DeleteEvent event) throws HibernateException { onDelete(event, new IdentitySet()); } /** * Handle the given delete event. This is the cascaded form. * * @param event The delete event. * @param transientEntities The cache of entities already deleted * @throws HibernateException */ public void onDelete(DeleteEvent event, Set transientEntities) throws HibernateException { final EventSource source = event.getSession(); final PersistenceContext persistenceContext = source.getPersistenceContext(); Object entity = persistenceContext.unproxyAndReassociate(event.getObject()); EntityEntry entityEntry = persistenceContext.getEntry(entity); final EntityPersister persister; final Serializable id; final Object version; if (entityEntry == null) { LOG.trace("Entity was not persistent in delete processing"); persister = source.getEntityPersister(event.getEntityName(), entity); if (ForeignKeys.isTransient(persister.getEntityName(), entity, null, source)) { deleteTransientEntity( source, entity, event.isCascadeDeleteEnabled(), persister, transientEntities); // EARLY EXIT!!! return; } performDetachedEntityDeletionCheck(event); id = persister.getIdentifier(entity, source); if (id == null) { throw new TransientObjectException( "the detached instance passed to delete() had a null identifier"); } final EntityKey key = source.generateEntityKey(id, persister); persistenceContext.checkUniqueness(key, entity); new OnUpdateVisitor(source, id, entity).process(entity, persister); version = persister.getVersion(entity); entityEntry = persistenceContext.addEntity( entity, (persister.isMutable() ? Status.MANAGED : Status.READ_ONLY), persister.getPropertyValues(entity), key, version, LockMode.NONE, true, persister, false); } else { LOG.trace("Deleting a persistent instance"); if (entityEntry.getStatus() == Status.DELETED || entityEntry.getStatus() == Status.GONE) { LOG.trace("Object was already deleted"); return; } persister = entityEntry.getPersister(); id = entityEntry.getId(); version = entityEntry.getVersion(); } /*if ( !persister.isMutable() ) { throw new HibernateException( "attempted to delete an object of immutable class: " + MessageHelper.infoString(persister) ); }*/ if (invokeDeleteLifecycle(source, entity, persister)) { return; } deleteEntity( source, entity, entityEntry, event.isCascadeDeleteEnabled(), event.isOrphanRemovalBeforeUpdates(), persister, transientEntities); if (source.getFactory().getSettings().isIdentifierRollbackEnabled()) { persister.resetIdentifier(entity, id, version, source); } } /** * Called when we have recognized an attempt to delete a detached entity. * * <p>This is perfectly valid in Hibernate usage; JPA, however, forbids this. Thus, this is a hook * for HEM to affect this behavior. * * @param event The event. */ protected void performDetachedEntityDeletionCheck(DeleteEvent event) { // ok in normal Hibernate usage to delete a detached entity; JPA however // forbids it, thus this is a hook for HEM to affect this behavior } /** * We encountered a delete request on a transient instance. * * <p>This is a deviation from historical Hibernate (pre-3.2) behavior to align with the JPA spec, * which states that transient entities can be passed to remove operation in which case cascades * still need to be performed. * * @param session The session which is the source of the event * @param entity The entity being delete processed * @param cascadeDeleteEnabled Is cascading of deletes enabled * @param persister The entity persister * @param transientEntities A cache of already visited transient entities (to avoid infinite * recursion). */ protected void deleteTransientEntity( EventSource session, Object entity, boolean cascadeDeleteEnabled, EntityPersister persister, Set transientEntities) { LOG.handlingTransientEntity(); if (transientEntities.contains(entity)) { LOG.trace("Already handled transient entity; skipping"); return; } transientEntities.add(entity); cascadeBeforeDelete(session, persister, entity, null, transientEntities); cascadeAfterDelete(session, persister, entity, transientEntities); } /** * Perform the entity deletion. Well, as with most operations, does not really perform it; just * schedules an action/execution with the {@link org.hibernate.engine.spi.ActionQueue} for * execution during flush. * * @param session The originating session * @param entity The entity to delete * @param entityEntry The entity's entry in the {@link PersistenceContext} * @param isCascadeDeleteEnabled Is delete cascading enabled? * @param persister The entity persister. * @param transientEntities A cache of already deleted entities. */ protected final void deleteEntity( final EventSource session, final Object entity, final EntityEntry entityEntry, final boolean isCascadeDeleteEnabled, final boolean isOrphanRemovalBeforeUpdates, final EntityPersister persister, final Set transientEntities) { if (LOG.isTraceEnabled()) { LOG.tracev( "Deleting {0}", MessageHelper.infoString(persister, entityEntry.getId(), session.getFactory())); } final PersistenceContext persistenceContext = session.getPersistenceContext(); final Type[] propTypes = persister.getPropertyTypes(); final Object version = entityEntry.getVersion(); final Object[] currentState; if (entityEntry.getLoadedState() == null) { // ie. the entity came in from update() currentState = persister.getPropertyValues(entity); } else { currentState = entityEntry.getLoadedState(); } final Object[] deletedState = createDeletedState(persister, currentState, session); entityEntry.setDeletedState(deletedState); session .getInterceptor() .onDelete( entity, entityEntry.getId(), deletedState, persister.getPropertyNames(), propTypes); // before any callbacks, etc, so subdeletions see that this deletion happened first persistenceContext.setEntryStatus(entityEntry, Status.DELETED); final EntityKey key = session.generateEntityKey(entityEntry.getId(), persister); cascadeBeforeDelete(session, persister, entity, entityEntry, transientEntities); new ForeignKeys.Nullifier(entity, true, false, session) .nullifyTransientReferences(entityEntry.getDeletedState(), propTypes); new Nullability(session).checkNullability(entityEntry.getDeletedState(), persister, true); persistenceContext.getNullifiableEntityKeys().add(key); if (isOrphanRemovalBeforeUpdates) { // TODO: The removeOrphan concept is a temporary "hack" for HHH-6484. This should be removed // once action/task // ordering is improved. session .getActionQueue() .addAction( new OrphanRemovalAction( entityEntry.getId(), deletedState, version, entity, persister, isCascadeDeleteEnabled, session)); } else { // Ensures that containing deletions happen before sub-deletions session .getActionQueue() .addAction( new EntityDeleteAction( entityEntry.getId(), deletedState, version, entity, persister, isCascadeDeleteEnabled, session)); } cascadeAfterDelete(session, persister, entity, transientEntities); // the entry will be removed after the flush, and will no longer // override the stale snapshot // This is now handled by removeEntity() in EntityDeleteAction // persistenceContext.removeDatabaseSnapshot(key); } private Object[] createDeletedState( EntityPersister persister, Object[] currentState, EventSource session) { Type[] propTypes = persister.getPropertyTypes(); final Object[] deletedState = new Object[propTypes.length]; // TypeFactory.deepCopy( currentState, propTypes, persister.getPropertyUpdateability(), // deletedState, session ); boolean[] copyability = new boolean[propTypes.length]; java.util.Arrays.fill(copyability, true); TypeHelper.deepCopy(currentState, propTypes, copyability, deletedState, session); return deletedState; } protected boolean invokeDeleteLifecycle( EventSource session, Object entity, EntityPersister persister) { if (persister.implementsLifecycle()) { LOG.debug("Calling onDelete()"); if (((Lifecycle) entity).onDelete(session)) { LOG.debug("Deletion vetoed by onDelete()"); return true; } } return false; } protected void cascadeBeforeDelete( EventSource session, EntityPersister persister, Object entity, EntityEntry entityEntry, Set transientEntities) throws HibernateException { CacheMode cacheMode = session.getCacheMode(); session.setCacheMode(CacheMode.GET); session.getPersistenceContext().incrementCascadeLevel(); try { // cascade-delete to collections BEFORE the collection owner is deleted Cascade.cascade( CascadingActions.DELETE, CascadePoint.AFTER_INSERT_BEFORE_DELETE, session, persister, entity, transientEntities); } finally { session.getPersistenceContext().decrementCascadeLevel(); session.setCacheMode(cacheMode); } } protected void cascadeAfterDelete( EventSource session, EntityPersister persister, Object entity, Set transientEntities) throws HibernateException { CacheMode cacheMode = session.getCacheMode(); session.setCacheMode(CacheMode.GET); session.getPersistenceContext().incrementCascadeLevel(); try { // cascade-delete to many-to-one AFTER the parent was deleted Cascade.cascade( CascadingActions.DELETE, CascadePoint.BEFORE_INSERT_AFTER_DELETE, session, persister, entity, transientEntities); } finally { session.getPersistenceContext().decrementCascadeLevel(); session.setCacheMode(cacheMode); } } }
/** * Defines the default load event listeners used by hibernate for loading entities in response to * generated load events. * * @author Eric Dalquist * @author Steve Ebersole */ public class DefaultResolveNaturalIdEventListener extends AbstractLockUpgradeEventListener implements ResolveNaturalIdEventListener { public static final Object REMOVED_ENTITY_MARKER = new Object(); public static final Object INCONSISTENT_RTN_CLASS_MARKER = new Object(); private static final CoreMessageLogger LOG = CoreLogging.messageLogger(DefaultResolveNaturalIdEventListener.class); @Override public void onResolveNaturalId(ResolveNaturalIdEvent event) throws HibernateException { final Serializable entityId = resolveNaturalId(event); event.setEntityId(entityId); } /** * Coordinates the efforts to load a given entity. First, an attempt is made to load the entity * from the session-level cache. If not found there, an attempt is made to locate it in * second-level cache. Lastly, an attempt is made to load it directly from the datasource. * * @param event The load event * @return The loaded entity, or null. */ protected Serializable resolveNaturalId(final ResolveNaturalIdEvent event) { final EntityPersister persister = event.getEntityPersister(); final boolean traceEnabled = LOG.isTraceEnabled(); if (traceEnabled) LOG.tracev( "Attempting to resolve: {0}", MessageHelper.infoString( persister, event.getNaturalIdValues(), event.getSession().getFactory())); Serializable entityId = resolveFromCache(event); if (entityId != null) { if (traceEnabled) LOG.tracev( "Resolved object in cache: {0}", MessageHelper.infoString( persister, event.getNaturalIdValues(), event.getSession().getFactory())); return entityId; } if (traceEnabled) LOG.tracev( "Object not resolved in any cache: {0}", MessageHelper.infoString( persister, event.getNaturalIdValues(), event.getSession().getFactory())); return loadFromDatasource(event); } /** * Attempts to resolve the entity id corresponding to the event's natural id values from the * session * * @param event The load event * @return The entity from the cache, or null. */ protected Serializable resolveFromCache(final ResolveNaturalIdEvent event) { return event .getSession() .getPersistenceContext() .getNaturalIdHelper() .findCachedNaturalIdResolution( event.getEntityPersister(), event.getOrderedNaturalIdValues()); } /** * Performs the process of loading an entity from the configured underlying datasource. * * @param event The load event * @return The object loaded from the datasource, or null if not found. */ protected Serializable loadFromDatasource(final ResolveNaturalIdEvent event) { final SessionFactoryImplementor factory = event.getSession().getFactory(); final boolean stats = factory.getStatistics().isStatisticsEnabled(); long startTime = 0; if (stats) { startTime = System.currentTimeMillis(); } final Serializable pk = event .getEntityPersister() .loadEntityIdByNaturalId( event.getOrderedNaturalIdValues(), event.getLockOptions(), event.getSession()); if (stats) { final NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy = event.getEntityPersister().getNaturalIdCacheAccessStrategy(); final String regionName = naturalIdCacheAccessStrategy == null ? null : naturalIdCacheAccessStrategy.getRegion().getName(); factory .getStatisticsImplementor() .naturalIdQueryExecuted(regionName, System.currentTimeMillis() - startTime); } // PK can be null if the entity doesn't exist if (pk != null) { event .getSession() .getPersistenceContext() .getNaturalIdHelper() .cacheNaturalIdCrossReferenceFromLoad( event.getEntityPersister(), pk, event.getOrderedNaturalIdValues()); } return pk; } }
/** * A convenience bas class for listeners responding to save events. * * @author Steve Ebersole. */ public abstract class AbstractSaveEventListener extends AbstractReassociateEventListener { private static final CoreMessageLogger LOG = CoreLogging.messageLogger(AbstractSaveEventListener.class); public static enum EntityState { PERSISTENT, TRANSIENT, DETACHED, DELETED } /** * Prepares the save call using the given requested id. * * @param entity The entity to be saved. * @param requestedId The id to which to associate the entity. * @param entityName The name of the entity being saved. * @param anything Generally cascade-specific information. * @param source The session which is the source of this save event. * @return The id used to save the entity. */ protected Serializable saveWithRequestedId( Object entity, Serializable requestedId, String entityName, Object anything, EventSource source) { return performSave( entity, requestedId, source.getEntityPersister(entityName, entity), false, anything, source, true); } /** * Prepares the save call using a newly generated id. * * @param entity The entity to be saved * @param entityName The entity-name for the entity to be saved * @param anything Generally cascade-specific information. * @param source The session which is the source of this save event. * @param requiresImmediateIdAccess does the event context require access to the identifier * immediately after execution of this method (if not, post-insert style id generators may be * postponed if we are outside a transaction). * @return The id used to save the entity; may be null depending on the type of id generator used * and the requiresImmediateIdAccess value */ protected Serializable saveWithGeneratedId( Object entity, String entityName, Object anything, EventSource source, boolean requiresImmediateIdAccess) { EntityPersister persister = source.getEntityPersister(entityName, entity); Serializable generatedId = persister.getIdentifierGenerator().generate(source, entity); if (generatedId == null) { throw new IdentifierGenerationException("null id generated for:" + entity.getClass()); } else if (generatedId == IdentifierGeneratorHelper.SHORT_CIRCUIT_INDICATOR) { return source.getIdentifier(entity); } else if (generatedId == IdentifierGeneratorHelper.POST_INSERT_INDICATOR) { return performSave( entity, null, persister, true, anything, source, requiresImmediateIdAccess); } else { // TODO: define toString()s for generators if (LOG.isDebugEnabled()) { LOG.debugf( "Generated identifier: %s, using strategy: %s", persister.getIdentifierType().toLoggableString(generatedId, source.getFactory()), persister.getIdentifierGenerator().getClass().getName()); } return performSave(entity, generatedId, persister, false, anything, source, true); } } /** * Prepares the save call by checking the session caches for a pre-existing entity and performing * any lifecycle callbacks. * * @param entity The entity to be saved. * @param id The id by which to save the entity. * @param persister The entity's persister instance. * @param useIdentityColumn Is an identity column being used? * @param anything Generally cascade-specific information. * @param source The session from which the event originated. * @param requiresImmediateIdAccess does the event context require access to the identifier * immediately after execution of this method (if not, post-insert style id generators may be * postponed if we are outside a transaction). * @return The id used to save the entity; may be null depending on the type of id generator used * and the requiresImmediateIdAccess value */ protected Serializable performSave( Object entity, Serializable id, EntityPersister persister, boolean useIdentityColumn, Object anything, EventSource source, boolean requiresImmediateIdAccess) { if (LOG.isTraceEnabled()) { LOG.tracev("Saving {0}", MessageHelper.infoString(persister, id, source.getFactory())); } final EntityKey key; if (!useIdentityColumn) { key = source.generateEntityKey(id, persister); Object old = source.getPersistenceContext().getEntity(key); if (old != null) { if (source.getPersistenceContext().getEntry(old).getStatus() == Status.DELETED) { source.forceFlush(source.getPersistenceContext().getEntry(old)); } else { throw new NonUniqueObjectException(id, persister.getEntityName()); } } persister.setIdentifier(entity, id, source); } else { key = null; } if (invokeSaveLifecycle(entity, persister, source)) { return id; // EARLY EXIT } return performSaveOrReplicate( entity, key, persister, useIdentityColumn, anything, source, requiresImmediateIdAccess); } protected boolean invokeSaveLifecycle( Object entity, EntityPersister persister, EventSource source) { // Sub-insertions should occur before containing insertion so // Try to do the callback now if (persister.implementsLifecycle()) { LOG.debug("Calling onSave()"); if (((Lifecycle) entity).onSave(source)) { LOG.debug("Insertion vetoed by onSave()"); return true; } } return false; } /** * Performs all the actual work needed to save an entity (well to get the save moved to the * execution queue). * * @param entity The entity to be saved * @param key The id to be used for saving the entity (or null, in the case of identity columns) * @param persister The entity's persister instance. * @param useIdentityColumn Should an identity column be used for id generation? * @param anything Generally cascade-specific information. * @param source The session which is the source of the current event. * @param requiresImmediateIdAccess Is access to the identifier required immediately after the * completion of the save? persist(), for example, does not require this... * @return The id used to save the entity; may be null depending on the type of id generator used * and the requiresImmediateIdAccess value */ protected Serializable performSaveOrReplicate( Object entity, EntityKey key, EntityPersister persister, boolean useIdentityColumn, Object anything, EventSource source, boolean requiresImmediateIdAccess) { Serializable id = key == null ? null : key.getIdentifier(); boolean inTxn = source.isTransactionInProgress(); boolean shouldDelayIdentityInserts = !inTxn && !requiresImmediateIdAccess; // Put a placeholder in entries, so we don't recurse back and try to save() the // same object again. QUESTION: should this be done before onSave() is called? // likewise, should it be done before onUpdate()? EntityEntry original = source .getPersistenceContext() .addEntry( entity, Status.SAVING, null, null, id, null, LockMode.WRITE, useIdentityColumn, persister, false, false); cascadeBeforeSave(source, persister, entity, anything); Object[] values = persister.getPropertyValuesToInsert(entity, getMergeMap(anything), source); Type[] types = persister.getPropertyTypes(); boolean substitute = substituteValuesIfNecessary(entity, id, values, persister, source); if (persister.hasCollections()) { substitute = substitute || visitCollectionsBeforeSave(entity, id, values, types, source); } if (substitute) { persister.setPropertyValues(entity, values); } TypeHelper.deepCopy(values, types, persister.getPropertyUpdateability(), values, source); AbstractEntityInsertAction insert = addInsertAction( values, id, entity, persister, useIdentityColumn, source, shouldDelayIdentityInserts); // postpone initializing id in case the insert has non-nullable transient dependencies // that are not resolved until cascadeAfterSave() is executed cascadeAfterSave(source, persister, entity, anything); if (useIdentityColumn && insert.isEarlyInsert()) { if (!EntityIdentityInsertAction.class.isInstance(insert)) { throw new IllegalStateException( "Insert should be using an identity column, but action is of unexpected type: " + insert.getClass().getName()); } id = ((EntityIdentityInsertAction) insert).getGeneratedId(); insert.handleNaturalIdPostSaveNotifications(id); } EntityEntry newEntry = source.getPersistenceContext().getEntry(entity); if (newEntry != original) { EntityEntryExtraState extraState = newEntry.getExtraState(EntityEntryExtraState.class); if (extraState == null) { newEntry.addExtraState(original.getExtraState(EntityEntryExtraState.class)); } } return id; } private AbstractEntityInsertAction addInsertAction( Object[] values, Serializable id, Object entity, EntityPersister persister, boolean useIdentityColumn, EventSource source, boolean shouldDelayIdentityInserts) { if (useIdentityColumn) { EntityIdentityInsertAction insert = new EntityIdentityInsertAction( values, entity, persister, isVersionIncrementDisabled(), source, shouldDelayIdentityInserts); source.getActionQueue().addAction(insert); return insert; } else { Object version = Versioning.getVersion(values, persister); EntityInsertAction insert = new EntityInsertAction( id, values, entity, version, persister, isVersionIncrementDisabled(), source); source.getActionQueue().addAction(insert); return insert; } } protected Map getMergeMap(Object anything) { return null; } /** * After the save, will te version number be incremented if the instance is modified? * * @return True if the version will be incremented on an entity change after save; false * otherwise. */ protected boolean isVersionIncrementDisabled() { return false; } protected boolean visitCollectionsBeforeSave( Object entity, Serializable id, Object[] values, Type[] types, EventSource source) { WrapVisitor visitor = new WrapVisitor(source); // substitutes into values by side-effect visitor.processEntityPropertyValues(values, types); return visitor.isSubstitutionRequired(); } /** * Perform any property value substitution that is necessary (interceptor callback, version * initialization...) * * @param entity The entity * @param id The entity identifier * @param values The snapshot entity state * @param persister The entity persister * @param source The originating session * @return True if the snapshot state changed such that reinjection of the values into the entity * is required. */ protected boolean substituteValuesIfNecessary( Object entity, Serializable id, Object[] values, EntityPersister persister, SessionImplementor source) { boolean substitute = source .getInterceptor() .onSave(entity, id, values, persister.getPropertyNames(), persister.getPropertyTypes()); // keep the existing version number in the case of replicate! if (persister.isVersioned()) { substitute = Versioning.seedVersion( values, persister.getVersionProperty(), persister.getVersionType(), source) || substitute; } return substitute; } /** * Handles the calls needed to perform pre-save cascades for the given entity. * * @param source The session from whcih the save event originated. * @param persister The entity's persister instance. * @param entity The entity to be saved. * @param anything Generally cascade-specific data */ protected void cascadeBeforeSave( EventSource source, EntityPersister persister, Object entity, Object anything) { // cascade-save to many-to-one BEFORE the parent is saved source.getPersistenceContext().incrementCascadeLevel(); try { Cascade.cascade( getCascadeAction(), CascadePoint.BEFORE_INSERT_AFTER_DELETE, source, persister, entity, anything); } finally { source.getPersistenceContext().decrementCascadeLevel(); } } /** * Handles to calls needed to perform post-save cascades. * * @param source The session from which the event originated. * @param persister The entity's persister instance. * @param entity The entity beng saved. * @param anything Generally cascade-specific data */ protected void cascadeAfterSave( EventSource source, EntityPersister persister, Object entity, Object anything) { // cascade-save to collections AFTER the collection owner was saved source.getPersistenceContext().incrementCascadeLevel(); try { Cascade.cascade( getCascadeAction(), CascadePoint.AFTER_INSERT_BEFORE_DELETE, source, persister, entity, anything); } finally { source.getPersistenceContext().decrementCascadeLevel(); } } protected abstract CascadingAction getCascadeAction(); /** * Determine whether the entity is persistent, detached, or transient * * @param entity The entity to check * @param entityName The name of the entity * @param entry The entity's entry in the persistence context * @param source The originating session. * @return The state. */ protected EntityState getEntityState( Object entity, String entityName, EntityEntry entry, // pass this as an argument only to avoid double looking SessionImplementor source) { final boolean traceEnabled = LOG.isTraceEnabled(); if (entry != null) { // the object is persistent // the entity is associated with the session, so check its status if (entry.getStatus() != Status.DELETED) { // do nothing for persistent instances if (traceEnabled) { LOG.tracev("Persistent instance of: {0}", getLoggableName(entityName, entity)); } return EntityState.PERSISTENT; } // ie. e.status==DELETED if (traceEnabled) { LOG.tracev("Deleted instance of: {0}", getLoggableName(entityName, entity)); } return EntityState.DELETED; } // the object is transient or detached // the entity is not associated with the session, so // try interceptor and unsaved-value if (ForeignKeys.isTransient(entityName, entity, getAssumedUnsaved(), source)) { if (traceEnabled) { LOG.tracev("Transient instance of: {0}", getLoggableName(entityName, entity)); } return EntityState.TRANSIENT; } if (traceEnabled) { LOG.tracev("Detached instance of: {0}", getLoggableName(entityName, entity)); } return EntityState.DETACHED; } protected String getLoggableName(String entityName, Object entity) { return entityName == null ? entity.getClass().getName() : entityName; } protected Boolean getAssumedUnsaved() { return null; } }
/** @author Steve Ebersole */ public class MetadataBuilderImpl implements MetadataBuilder, TypeContributions { private static final CoreMessageLogger log = CoreLogging.messageLogger(MetadataBuilderImpl.class); private final MetadataSources sources; private final MetadataBuildingOptionsImpl options; public MetadataBuilderImpl(MetadataSources sources) { this(sources, getStandardServiceRegistry(sources.getServiceRegistry())); } private static StandardServiceRegistry getStandardServiceRegistry( ServiceRegistry serviceRegistry) { if (serviceRegistry == null) { throw new HibernateException("ServiceRegistry passed to MetadataBuilder cannot be null"); } if (StandardServiceRegistry.class.isInstance(serviceRegistry)) { return (StandardServiceRegistry) serviceRegistry; } else if (BootstrapServiceRegistry.class.isInstance(serviceRegistry)) { log.debugf( "ServiceRegistry passed to MetadataBuilder was a BootstrapServiceRegistry; this likely wont end well" + "if attempt is made to build SessionFactory"); return new StandardServiceRegistryBuilder((BootstrapServiceRegistry) serviceRegistry).build(); } else { throw new HibernateException( String.format( "Unexpected type of ServiceRegistry [%s] encountered in attempt to build MetadataBuilder", serviceRegistry.getClass().getName())); } } public MetadataBuilderImpl(MetadataSources sources, StandardServiceRegistry serviceRegistry) { this.sources = sources; this.options = new MetadataBuildingOptionsImpl(serviceRegistry); for (MetadataSourcesContributor contributor : sources .getServiceRegistry() .getService(ClassLoaderService.class) .loadJavaServices(MetadataSourcesContributor.class)) { contributor.contribute(sources); } // todo : not so sure this is needed anymore. // these should be set during the StandardServiceRegistryBuilder.configure call applyCfgXmlValues(serviceRegistry.getService(CfgXmlAccessService.class)); final ClassLoaderService classLoaderService = serviceRegistry.getService(ClassLoaderService.class); for (MetadataBuilderInitializer contributor : classLoaderService.loadJavaServices(MetadataBuilderInitializer.class)) { contributor.contribute(this, serviceRegistry); } } private void applyCfgXmlValues(CfgXmlAccessService service) { final LoadedConfig aggregatedConfig = service.getAggregatedConfig(); if (aggregatedConfig == null) { return; } for (CacheRegionDefinition cacheRegionDefinition : aggregatedConfig.getCacheRegionDefinitions()) { applyCacheRegionDefinition(cacheRegionDefinition); } } @Override public MetadataBuilder applyImplicitSchemaName(String implicitSchemaName) { options.mappingDefaults.implicitSchemaName = implicitSchemaName; return this; } @Override public MetadataBuilder applyImplicitCatalogName(String implicitCatalogName) { options.mappingDefaults.implicitCatalogName = implicitCatalogName; return this; } @Override public MetadataBuilder applyImplicitNamingStrategy(ImplicitNamingStrategy namingStrategy) { this.options.implicitNamingStrategy = namingStrategy; return this; } @Override public MetadataBuilder applyPhysicalNamingStrategy(PhysicalNamingStrategy namingStrategy) { this.options.physicalNamingStrategy = namingStrategy; return this; } @Override public MetadataBuilder applyReflectionManager(ReflectionManager reflectionManager) { this.options.reflectionManager = reflectionManager; this.options.reflectionManager.injectClassLoaderDelegate( this.options.getHcannClassLoaderDelegate()); return this; } @Override public MetadataBuilder applySharedCacheMode(SharedCacheMode sharedCacheMode) { this.options.sharedCacheMode = sharedCacheMode; return this; } @Override public MetadataBuilder applyAccessType(AccessType implicitCacheAccessType) { this.options.mappingDefaults.implicitCacheAccessType = implicitCacheAccessType; return this; } @Override public MetadataBuilder applyIndexView(IndexView jandexView) { this.options.jandexView = jandexView; return this; } @Override public MetadataBuilder applyScanOptions(ScanOptions scanOptions) { this.options.scanOptions = scanOptions; return this; } @Override public MetadataBuilder applyScanEnvironment(ScanEnvironment scanEnvironment) { this.options.scanEnvironment = scanEnvironment; return this; } @Override public MetadataBuilder applyScanner(Scanner scanner) { this.options.scannerSetting = scanner; return this; } @Override public MetadataBuilder applyArchiveDescriptorFactory(ArchiveDescriptorFactory factory) { this.options.archiveDescriptorFactory = factory; return this; } @Override public MetadataBuilder enableExplicitDiscriminatorsForJoinedSubclassSupport(boolean supported) { options.explicitDiscriminatorsForJoinedInheritanceSupported = supported; return this; } @Override public MetadataBuilder enableImplicitDiscriminatorsForJoinedSubclassSupport(boolean supported) { options.implicitDiscriminatorsForJoinedInheritanceSupported = supported; return this; } @Override public MetadataBuilder enableImplicitForcingOfDiscriminatorsInSelect(boolean supported) { options.implicitlyForceDiscriminatorInSelect = supported; return this; } @Override public MetadataBuilder enableGlobalNationalizedCharacterDataSupport(boolean enabled) { options.useNationalizedCharacterData = enabled; return this; } @Override public MetadataBuilder applyBasicType(BasicType type) { options.basicTypeRegistrations.add(type); return this; } @Override public MetadataBuilder applyBasicType(UserType type, String[] keys) { options.basicTypeRegistrations.add(new CustomType(type, keys)); return this; } @Override public MetadataBuilder applyBasicType(CompositeUserType type, String[] keys) { options.basicTypeRegistrations.add(new CompositeCustomType(type, keys)); return this; } @Override public MetadataBuilder applyTypes(TypeContributor typeContributor) { typeContributor.contribute(this, options.serviceRegistry); return this; } @Override public void contributeType(BasicType type) { options.basicTypeRegistrations.add(type); } @Override public void contributeType(UserType type, String[] keys) { options.basicTypeRegistrations.add(new CustomType(type, keys)); } @Override public void contributeType(CompositeUserType type, String[] keys) { options.basicTypeRegistrations.add(new CompositeCustomType(type, keys)); } @Override public MetadataBuilder applyCacheRegionDefinition(CacheRegionDefinition cacheRegionDefinition) { if (options.cacheRegionDefinitions == null) { options.cacheRegionDefinitions = new ArrayList<CacheRegionDefinition>(); } options.cacheRegionDefinitions.add(cacheRegionDefinition); return this; } @Override public MetadataBuilder applyTempClassLoader(ClassLoader tempClassLoader) { options.tempClassLoader = tempClassLoader; return this; } @Override public MetadataBuilder applySourceProcessOrdering(MetadataSourceType... sourceTypes) { options.sourceProcessOrdering.addAll(Arrays.asList(sourceTypes)); return this; } public MetadataBuilder allowSpecjSyntax() { this.options.specjProprietarySyntaxEnabled = true; return this; } @Override public MetadataBuilder applySqlFunction(String functionName, SQLFunction function) { if (this.options.sqlFunctionMap == null) { this.options.sqlFunctionMap = new HashMap<String, SQLFunction>(); } this.options.sqlFunctionMap.put(functionName, function); return this; } @Override public MetadataBuilder applyAuxiliaryDatabaseObject( AuxiliaryDatabaseObject auxiliaryDatabaseObject) { if (this.options.auxiliaryDatabaseObjectList == null) { this.options.auxiliaryDatabaseObjectList = new ArrayList<AuxiliaryDatabaseObject>(); } this.options.auxiliaryDatabaseObjectList.add(auxiliaryDatabaseObject); return this; } @Override public MetadataBuilder applyAttributeConverter(AttributeConverterDefinition definition) { this.options.addAttributeConverterDefinition(definition); return this; } @Override public MetadataBuilder applyAttributeConverter( Class<? extends AttributeConverter> attributeConverterClass) { applyAttributeConverter(AttributeConverterDefinition.from(attributeConverterClass)); return this; } @Override public MetadataBuilder applyAttributeConverter( Class<? extends AttributeConverter> attributeConverterClass, boolean autoApply) { applyAttributeConverter(AttributeConverterDefinition.from(attributeConverterClass, autoApply)); return this; } @Override public MetadataBuilder applyAttributeConverter(AttributeConverter attributeConverter) { applyAttributeConverter(AttributeConverterDefinition.from(attributeConverter)); return this; } @Override public MetadataBuilder applyAttributeConverter( AttributeConverter attributeConverter, boolean autoApply) { applyAttributeConverter(AttributeConverterDefinition.from(attributeConverter, autoApply)); return this; } @Override public MetadataBuilder enableNewIdentifierGeneratorSupport(boolean enabled) { if (enabled) { this.options.idGenerationTypeInterpreter.disableLegacyFallback(); } else { this.options.idGenerationTypeInterpreter.enableLegacyFallback(); } return this; } @Override public MetadataBuilder applyIdGenerationTypeInterpreter( IdGeneratorStrategyInterpreter interpreter) { this.options.idGenerationTypeInterpreter.addInterpreterDelegate(interpreter); return this; } // public MetadataBuilder with(PersistentAttributeMemberResolver resolver) { // options.persistentAttributeMemberResolver = resolver; // return this; // } @Override public MetadataImpl build() { final CfgXmlAccessService cfgXmlAccessService = options.serviceRegistry.getService(CfgXmlAccessService.class); if (cfgXmlAccessService.getAggregatedConfig() != null) { if (cfgXmlAccessService.getAggregatedConfig().getMappingReferences() != null) { for (MappingReference mappingReference : cfgXmlAccessService.getAggregatedConfig().getMappingReferences()) { mappingReference.apply(sources); } } } return MetadataBuildingProcess.build(sources, options); } public static class MappingDefaultsImpl implements MappingDefaults { private String implicitSchemaName; private String implicitCatalogName; private boolean implicitlyQuoteIdentifiers; private AccessType implicitCacheAccessType; public MappingDefaultsImpl(StandardServiceRegistry serviceRegistry) { final ConfigurationService configService = serviceRegistry.getService(ConfigurationService.class); this.implicitSchemaName = configService.getSetting( AvailableSettings.DEFAULT_SCHEMA, StandardConverters.STRING, null); this.implicitCatalogName = configService.getSetting( AvailableSettings.DEFAULT_CATALOG, StandardConverters.STRING, null); this.implicitlyQuoteIdentifiers = configService.getSetting( AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS, StandardConverters.BOOLEAN, false); this.implicitCacheAccessType = configService.getSetting( AvailableSettings.DEFAULT_CACHE_CONCURRENCY_STRATEGY, new ConfigurationService.Converter<AccessType>() { @Override public AccessType convert(Object value) { return AccessType.fromExternalName(value.toString()); } }); } @Override public String getImplicitSchemaName() { return implicitSchemaName; } @Override public String getImplicitCatalogName() { return implicitCatalogName; } @Override public boolean shouldImplicitlyQuoteIdentifiers() { return implicitlyQuoteIdentifiers; } @Override public String getImplicitIdColumnName() { return DEFAULT_IDENTIFIER_COLUMN_NAME; } @Override public String getImplicitTenantIdColumnName() { return DEFAULT_TENANT_IDENTIFIER_COLUMN_NAME; } @Override public String getImplicitDiscriminatorColumnName() { return DEFAULT_DISCRIMINATOR_COLUMN_NAME; } @Override public String getImplicitPackageName() { return null; } @Override public boolean isAutoImportEnabled() { return true; } @Override public String getImplicitCascadeStyleName() { return DEFAULT_CASCADE_NAME; } @Override public String getImplicitPropertyAccessorName() { return DEFAULT_PROPERTY_ACCESS_NAME; } @Override public boolean areEntitiesImplicitlyLazy() { // for now, just hard-code return false; } @Override public boolean areCollectionsImplicitlyLazy() { // for now, just hard-code return true; } @Override public AccessType getImplicitCacheAccessType() { return implicitCacheAccessType; } } public static class MetadataBuildingOptionsImpl implements MetadataBuildingOptions { private final StandardServiceRegistry serviceRegistry; private final MappingDefaultsImpl mappingDefaults; private ArrayList<BasicType> basicTypeRegistrations = new ArrayList<BasicType>(); private IndexView jandexView; private ClassLoader tempClassLoader; private ScanOptions scanOptions; private ScanEnvironment scanEnvironment; private Object scannerSetting; private ArchiveDescriptorFactory archiveDescriptorFactory; private ImplicitNamingStrategy implicitNamingStrategy; private PhysicalNamingStrategy physicalNamingStrategy; private ReflectionManager reflectionManager; private ClassLoaderDelegate hcannClassLoaderDelegate; private SharedCacheMode sharedCacheMode; private AccessType defaultCacheAccessType; private MultiTenancyStrategy multiTenancyStrategy; private ArrayList<CacheRegionDefinition> cacheRegionDefinitions; private boolean explicitDiscriminatorsForJoinedInheritanceSupported; private boolean implicitDiscriminatorsForJoinedInheritanceSupported; private boolean implicitlyForceDiscriminatorInSelect; private boolean useNationalizedCharacterData; private boolean specjProprietarySyntaxEnabled; private ArrayList<MetadataSourceType> sourceProcessOrdering; private HashMap<String, SQLFunction> sqlFunctionMap; private ArrayList<AuxiliaryDatabaseObject> auxiliaryDatabaseObjectList; private HashMap<Class, AttributeConverterDefinition> attributeConverterDefinitionsByClass; private IdGeneratorInterpreterImpl idGenerationTypeInterpreter = new IdGeneratorInterpreterImpl(); private boolean autoQuoteKeywords; // private PersistentAttributeMemberResolver persistentAttributeMemberResolver = // StandardPersistentAttributeMemberResolver.INSTANCE; public MetadataBuildingOptionsImpl(StandardServiceRegistry serviceRegistry) { this.serviceRegistry = serviceRegistry; final StrategySelector strategySelector = serviceRegistry.getService(StrategySelector.class); final ConfigurationService configService = serviceRegistry.getService(ConfigurationService.class); this.mappingDefaults = new MappingDefaultsImpl(serviceRegistry); // jandexView = (IndexView) configService.getSettings().get( AvailableSettings.JANDEX_INDEX // ); scanOptions = new StandardScanOptions( (String) configService.getSettings().get(AvailableSettings.SCANNER_DISCOVERY), false); // ScanEnvironment must be set explicitly scannerSetting = configService.getSettings().get(AvailableSettings.SCANNER); if (scannerSetting == null) { scannerSetting = configService.getSettings().get(AvailableSettings.SCANNER_DEPRECATED); if (scannerSetting != null) { DEPRECATION_LOGGER.logDeprecatedScannerSetting(); } } archiveDescriptorFactory = strategySelector.resolveStrategy( ArchiveDescriptorFactory.class, configService.getSettings().get(AvailableSettings.SCANNER_ARCHIVE_INTERPRETER)); multiTenancyStrategy = MultiTenancyStrategy.determineMultiTenancyStrategy(configService.getSettings()); implicitDiscriminatorsForJoinedInheritanceSupported = configService.getSetting( AvailableSettings.IMPLICIT_DISCRIMINATOR_COLUMNS_FOR_JOINED_SUBCLASS, StandardConverters.BOOLEAN, false); explicitDiscriminatorsForJoinedInheritanceSupported = !configService.getSetting( AvailableSettings.IGNORE_EXPLICIT_DISCRIMINATOR_COLUMNS_FOR_JOINED_SUBCLASS, StandardConverters.BOOLEAN, false); implicitlyForceDiscriminatorInSelect = configService.getSetting( AvailableSettings.FORCE_DISCRIMINATOR_IN_SELECTS_BY_DEFAULT, StandardConverters.BOOLEAN, false); sharedCacheMode = configService.getSetting( "javax.persistence.sharedCache.mode", new ConfigurationService.Converter<SharedCacheMode>() { @Override public SharedCacheMode convert(Object value) { if (value == null) { return null; } if (SharedCacheMode.class.isInstance(value)) { return (SharedCacheMode) value; } return SharedCacheMode.valueOf(value.toString()); } }, SharedCacheMode.UNSPECIFIED); defaultCacheAccessType = configService.getSetting( AvailableSettings.DEFAULT_CACHE_CONCURRENCY_STRATEGY, new ConfigurationService.Converter<AccessType>() { @Override public AccessType convert(Object value) { if (value == null) { return null; } if (CacheConcurrencyStrategy.class.isInstance(value)) { return ((CacheConcurrencyStrategy) value).toAccessType(); } if (AccessType.class.isInstance(value)) { return (AccessType) value; } return AccessType.fromExternalName(value.toString()); } }, // by default, see if the defined RegionFactory (if one) defines a default serviceRegistry.getService(RegionFactory.class) == null ? null : serviceRegistry.getService(RegionFactory.class).getDefaultAccessType()); specjProprietarySyntaxEnabled = configService.getSetting( "hibernate.enable_specj_proprietary_syntax", StandardConverters.BOOLEAN, false); implicitNamingStrategy = strategySelector.resolveDefaultableStrategy( ImplicitNamingStrategy.class, configService.getSettings().get(AvailableSettings.IMPLICIT_NAMING_STRATEGY), ImplicitNamingStrategyLegacyJpaImpl.INSTANCE); physicalNamingStrategy = strategySelector.resolveDefaultableStrategy( PhysicalNamingStrategy.class, configService.getSettings().get(AvailableSettings.PHYSICAL_NAMING_STRATEGY), PhysicalNamingStrategyStandardImpl.INSTANCE); sourceProcessOrdering = resolveInitialSourceProcessOrdering(configService); final boolean useNewIdentifierGenerators = configService.getSetting( AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, StandardConverters.BOOLEAN, false); if (useNewIdentifierGenerators) { idGenerationTypeInterpreter.disableLegacyFallback(); } else { idGenerationTypeInterpreter.enableLegacyFallback(); } reflectionManager = generateDefaultReflectionManager(); } private ArrayList<MetadataSourceType> resolveInitialSourceProcessOrdering( ConfigurationService configService) { final ArrayList<MetadataSourceType> initialSelections = new ArrayList<MetadataSourceType>(); final String sourceProcessOrderingSetting = configService.getSetting( AvailableSettings.ARTIFACT_PROCESSING_ORDER, StandardConverters.STRING); if (sourceProcessOrderingSetting != null) { final String[] orderChoices = StringHelper.split(",; ", sourceProcessOrderingSetting, false); initialSelections.addAll( CollectionHelper.<MetadataSourceType>arrayList(orderChoices.length)); for (String orderChoice : orderChoices) { initialSelections.add(MetadataSourceType.parsePrecedence(orderChoice)); } } if (initialSelections.isEmpty()) { initialSelections.add(MetadataSourceType.HBM); initialSelections.add(MetadataSourceType.CLASS); } return initialSelections; } private ReflectionManager generateDefaultReflectionManager() { final JavaReflectionManager reflectionManager = new JavaReflectionManager(); reflectionManager.setMetadataProvider(new JPAMetadataProvider(this)); reflectionManager.injectClassLoaderDelegate(getHcannClassLoaderDelegate()); return reflectionManager; } public ClassLoaderDelegate getHcannClassLoaderDelegate() { if (hcannClassLoaderDelegate == null) { hcannClassLoaderDelegate = new ClassLoaderDelegate() { private final ClassLoaderService classLoaderService = getServiceRegistry().getService(ClassLoaderService.class); @Override public <T> Class<T> classForName(String className) throws ClassLoadingException { try { return classLoaderService.classForName(className); } catch (org.hibernate.boot.registry.classloading.spi.ClassLoadingException e) { return StandardClassLoaderDelegateImpl.INSTANCE.classForName(className); } } }; } return hcannClassLoaderDelegate; } @Override public StandardServiceRegistry getServiceRegistry() { return serviceRegistry; } @Override public MappingDefaults getMappingDefaults() { return mappingDefaults; } @Override public List<BasicType> getBasicTypeRegistrations() { return basicTypeRegistrations; } @Override public IndexView getJandexView() { return jandexView; } @Override public ScanOptions getScanOptions() { return scanOptions; } @Override public ScanEnvironment getScanEnvironment() { return scanEnvironment; } @Override public Object getScanner() { return scannerSetting; } @Override public ArchiveDescriptorFactory getArchiveDescriptorFactory() { return archiveDescriptorFactory; } @Override public ClassLoader getTempClassLoader() { return tempClassLoader; } @Override public ImplicitNamingStrategy getImplicitNamingStrategy() { return implicitNamingStrategy; } @Override public PhysicalNamingStrategy getPhysicalNamingStrategy() { return physicalNamingStrategy; } @Override public ReflectionManager getReflectionManager() { return reflectionManager; } @Override public SharedCacheMode getSharedCacheMode() { return sharedCacheMode; } @Override public AccessType getImplicitCacheAccessType() { return defaultCacheAccessType; } @Override public MultiTenancyStrategy getMultiTenancyStrategy() { return multiTenancyStrategy; } @Override public IdGeneratorStrategyInterpreter getIdGenerationTypeInterpreter() { return idGenerationTypeInterpreter; } @Override public List<CacheRegionDefinition> getCacheRegionDefinitions() { return cacheRegionDefinitions; } @Override public boolean ignoreExplicitDiscriminatorsForJoinedInheritance() { return !explicitDiscriminatorsForJoinedInheritanceSupported; } @Override public boolean createImplicitDiscriminatorsForJoinedInheritance() { return implicitDiscriminatorsForJoinedInheritanceSupported; } @Override public boolean shouldImplicitlyForceDiscriminatorInSelect() { return implicitlyForceDiscriminatorInSelect; } @Override public boolean useNationalizedCharacterData() { return useNationalizedCharacterData; } @Override public boolean isSpecjProprietarySyntaxEnabled() { return specjProprietarySyntaxEnabled; } @Override public List<MetadataSourceType> getSourceProcessOrdering() { return sourceProcessOrdering; } @Override public Map<String, SQLFunction> getSqlFunctions() { return sqlFunctionMap == null ? Collections.<String, SQLFunction>emptyMap() : sqlFunctionMap; } @Override public List<AuxiliaryDatabaseObject> getAuxiliaryDatabaseObjectList() { return auxiliaryDatabaseObjectList == null ? Collections.<AuxiliaryDatabaseObject>emptyList() : auxiliaryDatabaseObjectList; } @Override public List<AttributeConverterDefinition> getAttributeConverters() { return attributeConverterDefinitionsByClass == null ? Collections.<AttributeConverterDefinition>emptyList() : new ArrayList<AttributeConverterDefinition>( attributeConverterDefinitionsByClass.values()); } public void addAttributeConverterDefinition(AttributeConverterDefinition definition) { if (this.attributeConverterDefinitionsByClass == null) { this.attributeConverterDefinitionsByClass = new HashMap<Class, AttributeConverterDefinition>(); } final Object old = this.attributeConverterDefinitionsByClass.put( definition.getAttributeConverter().getClass(), definition); if (old != null) { throw new AssertionFailure( String.format( "AttributeConverter class [%s] registered multiple times", definition.getAttributeConverter().getClass())); } } public static interface JpaOrmXmlPersistenceUnitDefaults { public String getDefaultSchemaName(); public String getDefaultCatalogName(); public boolean shouldImplicitlyQuoteIdentifiers(); } /** * Yuck. This is needed because JPA lets users define "global building options" in {@code * orm.xml} mappings. Forget that there are generally multiple {@code orm.xml} mappings if using * XML approach... Ugh */ public void apply(JpaOrmXmlPersistenceUnitDefaults jpaOrmXmlPersistenceUnitDefaults) { if (!mappingDefaults.shouldImplicitlyQuoteIdentifiers()) { mappingDefaults.implicitlyQuoteIdentifiers = jpaOrmXmlPersistenceUnitDefaults.shouldImplicitlyQuoteIdentifiers(); } if (mappingDefaults.getImplicitCatalogName() == null) { mappingDefaults.implicitCatalogName = StringHelper.nullIfEmpty(jpaOrmXmlPersistenceUnitDefaults.getDefaultCatalogName()); } if (mappingDefaults.getImplicitSchemaName() == null) { mappingDefaults.implicitSchemaName = StringHelper.nullIfEmpty(jpaOrmXmlPersistenceUnitDefaults.getDefaultSchemaName()); } } // @Override // public PersistentAttributeMemberResolver getPersistentAttributeMemberResolver() { // return persistentAttributeMemberResolver; // } } }
/** * A registry of {@link BasicType} instances * * @author Steve Ebersole */ public class BasicTypeRegistry implements Serializable { private static final CoreMessageLogger LOG = CoreLogging.messageLogger(BasicTypeRegistry.class); // TODO : analyze these sizing params; unfortunately this seems to be the only way to give a // "concurrencyLevel" private Map<String, BasicType> registry = new ConcurrentHashMap<String, BasicType>(100, .75f, 1); private boolean locked; public BasicTypeRegistry() { register(BooleanType.INSTANCE); register(NumericBooleanType.INSTANCE); register(TrueFalseType.INSTANCE); register(YesNoType.INSTANCE); register(ByteType.INSTANCE); register(CharacterType.INSTANCE); register(ShortType.INSTANCE); register(IntegerType.INSTANCE); register(LongType.INSTANCE); register(FloatType.INSTANCE); register(DoubleType.INSTANCE); register(BigDecimalType.INSTANCE); register(BigIntegerType.INSTANCE); register(StringType.INSTANCE); register(StringNVarcharType.INSTANCE); register(CharacterNCharType.INSTANCE); register(UrlType.INSTANCE); register(DateType.INSTANCE); register(TimeType.INSTANCE); register(TimestampType.INSTANCE); register(DbTimestampType.INSTANCE); register(CalendarType.INSTANCE); register(CalendarDateType.INSTANCE); register(LocaleType.INSTANCE); register(CurrencyType.INSTANCE); register(TimeZoneType.INSTANCE); register(ClassType.INSTANCE); register(UUIDBinaryType.INSTANCE); register(UUIDCharType.INSTANCE); register(BinaryType.INSTANCE); register(WrapperBinaryType.INSTANCE); register(ImageType.INSTANCE); register(CharArrayType.INSTANCE); register(CharacterArrayType.INSTANCE); register(TextType.INSTANCE); register(NTextType.INSTANCE); register(BlobType.INSTANCE); register(MaterializedBlobType.INSTANCE); register(ClobType.INSTANCE); register(NClobType.INSTANCE); register(MaterializedClobType.INSTANCE); register(MaterializedNClobType.INSTANCE); register(SerializableType.INSTANCE); register(ObjectType.INSTANCE); //noinspection unchecked register(new AdaptedImmutableType(DateType.INSTANCE)); //noinspection unchecked register(new AdaptedImmutableType(TimeType.INSTANCE)); //noinspection unchecked register(new AdaptedImmutableType(TimestampType.INSTANCE)); //noinspection unchecked register(new AdaptedImmutableType(DbTimestampType.INSTANCE)); //noinspection unchecked register(new AdaptedImmutableType(CalendarType.INSTANCE)); //noinspection unchecked register(new AdaptedImmutableType(CalendarDateType.INSTANCE)); //noinspection unchecked register(new AdaptedImmutableType(BinaryType.INSTANCE)); //noinspection unchecked register(new AdaptedImmutableType(SerializableType.INSTANCE)); } /** * Constructor version used during shallow copy * * @param registeredTypes The type map to copy over */ @SuppressWarnings({"UnusedDeclaration"}) private BasicTypeRegistry(Map<String, BasicType> registeredTypes) { registry.putAll(registeredTypes); locked = true; } public void register(BasicType type) { if (locked) { throw new HibernateException("Can not alter TypeRegistry at this time"); } if (type == null) { throw new HibernateException("Type to register cannot be null"); } if (type.getRegistrationKeys() == null || type.getRegistrationKeys().length == 0) { LOG.typeDefinedNoRegistrationKeys(type); } for (String key : type.getRegistrationKeys()) { // be safe... if (key == null) continue; LOG.debugf("Adding type registration %s -> %s", key, type); final Type old = registry.put(key, type); if (old != null && old != type) LOG.typeRegistrationOverridesPrevious(key, old); } } public void register(UserType type, String[] keys) { register(new CustomType(type, keys)); } public void register(CompositeUserType type, String[] keys) { register(new CompositeCustomType(type, keys)); } public BasicType getRegisteredType(String key) { return registry.get(key); } public BasicTypeRegistry shallowCopy() { return new BasicTypeRegistry(this.registry); } }
/** * An {@link EntityTuplizer} specific to the dynamic-map entity mode. * * @author Steve Ebersole * @author Gavin King */ public class DynamicMapEntityTuplizer extends AbstractEntityTuplizer { private static final CoreMessageLogger LOG = CoreLogging.messageLogger(DynamicMapEntityTuplizer.class); DynamicMapEntityTuplizer(EntityMetamodel entityMetamodel, PersistentClass mappedEntity) { super(entityMetamodel, mappedEntity); } @Override public EntityMode getEntityMode() { return EntityMode.MAP; } private PropertyAccessor buildPropertyAccessor(Property mappedProperty) { if (mappedProperty.isBackRef()) { return mappedProperty.getPropertyAccessor(null); } else { return PropertyAccessorFactory.getDynamicMapPropertyAccessor(); } } @Override protected Getter buildPropertyGetter(Property mappedProperty, PersistentClass mappedEntity) { return buildPropertyAccessor(mappedProperty).getGetter(null, mappedProperty.getName()); } @Override protected Setter buildPropertySetter(Property mappedProperty, PersistentClass mappedEntity) { return buildPropertyAccessor(mappedProperty).getSetter(null, mappedProperty.getName()); } @Override protected Instantiator buildInstantiator(PersistentClass mappingInfo) { return new DynamicMapInstantiator(mappingInfo); } @Override protected ProxyFactory buildProxyFactory( PersistentClass mappingInfo, Getter idGetter, Setter idSetter) { ProxyFactory pf = new MapProxyFactory(); try { // TODO: design new lifecycle for ProxyFactory pf.postInstantiate(getEntityName(), null, null, null, null, null); } catch (HibernateException he) { LOG.unableToCreateProxyFactory(getEntityName(), he); pf = null; } return pf; } @Override public Class getMappedClass() { return Map.class; } @Override public Class getConcreteProxyClass() { return Map.class; } @Override public boolean isInstrumented() { return false; } @Override public EntityNameResolver[] getEntityNameResolvers() { return new EntityNameResolver[] {BasicEntityNameResolver.INSTANCE}; } @Override public String determineConcreteSubclassEntityName( Object entityInstance, SessionFactoryImplementor factory) { return extractEmbeddedEntityName((Map) entityInstance); } public static String extractEmbeddedEntityName(Map entity) { return (String) entity.get(DynamicMapInstantiator.KEY); } public static class BasicEntityNameResolver implements EntityNameResolver { public static final BasicEntityNameResolver INSTANCE = new BasicEntityNameResolver(); @Override public String resolveEntityName(Object entity) { if (!Map.class.isInstance(entity)) { return null; } final String entityName = extractEmbeddedEntityName((Map) entity); if (entityName == null) { throw new HibernateException("Could not determine type of dynamic map entity"); } return entityName; } @Override public boolean equals(Object obj) { return getClass().equals(obj.getClass()); } @Override public int hashCode() { return getClass().hashCode(); } } }
/** * enhancer for persistent attributes of any type of entity * * @author <a href="mailto:[email protected]">Luis Barreiro</a> */ public class PersistentAttributesEnhancer extends Enhancer { private static final CoreMessageLogger log = CoreLogging.messageLogger(PersistentAttributesEnhancer.class); public PersistentAttributesEnhancer(EnhancementContext context) { super(context); } public void enhance(CtClass managedCtClass) { final IdentityHashMap<String, PersistentAttributeAccessMethods> attrDescriptorMap = new IdentityHashMap<String, PersistentAttributeAccessMethods>(); for (CtField persistentField : collectPersistentFields(managedCtClass)) { attrDescriptorMap.put( persistentField.getName(), enhancePersistentAttribute(managedCtClass, persistentField)); } // find all references to the transformed fields and replace with calls to the added // reader/writer methods enhanceAttributesAccess(managedCtClass, attrDescriptorMap); // same thing for direct access to fields of other entities if (this.enhancementContext.doFieldAccessEnhancement(managedCtClass)) { enhanceFieldAccess(managedCtClass); } } private CtField[] collectPersistentFields(CtClass managedCtClass) { final List<CtField> persistentFieldList = new LinkedList<CtField>(); for (CtField ctField : managedCtClass.getDeclaredFields()) { // skip static fields and skip fields added by enhancement if (Modifier.isStatic(ctField.getModifiers()) || ctField.getName().startsWith("$$_hibernate_")) { continue; } // skip outer reference in inner classes if ("this$0".equals(ctField.getName())) { continue; } if (enhancementContext.isPersistentField(ctField)) { persistentFieldList.add(ctField); } } return enhancementContext.order( persistentFieldList.toArray(new CtField[persistentFieldList.size()])); } private PersistentAttributeAccessMethods enhancePersistentAttribute( CtClass managedCtClass, CtField persistentField) { try { final AttributeTypeDescriptor typeDescriptor = AttributeTypeDescriptor.resolve(persistentField); return new PersistentAttributeAccessMethods( generateFieldReader(managedCtClass, persistentField, typeDescriptor), generateFieldWriter(managedCtClass, persistentField, typeDescriptor)); } catch (Exception e) { final String msg = String.format( "Unable to enhance persistent attribute [%s:%s]", managedCtClass.getName(), persistentField.getName()); throw new EnhancementException(msg, e); } } private CtMethod generateFieldReader( CtClass managedCtClass, CtField persistentField, AttributeTypeDescriptor typeDescriptor) { final String fieldName = persistentField.getName(); final String readerName = EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + fieldName; // read attempts only have to deal lazy-loading support, not dirty checking; // so if the field is not enabled as lazy-loadable return a plain simple getter as the reader if (!enhancementContext.isLazyLoadable(persistentField)) { return MethodWriter.addGetter(managedCtClass, fieldName, readerName); } try { return MethodWriter.write( managedCtClass, "public %s %s() {%n %s%n return this.%s;%n}", persistentField.getType().getName(), readerName, typeDescriptor.buildReadInterceptionBodyFragment(fieldName), fieldName); } catch (CannotCompileException cce) { final String msg = String.format( "Could not enhance entity class [%s] to add field reader method [%s]", managedCtClass.getName(), readerName); throw new EnhancementException(msg, cce); } catch (NotFoundException nfe) { final String msg = String.format( "Could not enhance entity class [%s] to add field reader method [%s]", managedCtClass.getName(), readerName); throw new EnhancementException(msg, nfe); } } private CtMethod generateFieldWriter( CtClass managedCtClass, CtField persistentField, AttributeTypeDescriptor typeDescriptor) { final String fieldName = persistentField.getName(); final String writerName = EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + fieldName; try { final CtMethod writer; if (!enhancementContext.isLazyLoadable(persistentField)) { writer = MethodWriter.addSetter(managedCtClass, fieldName, writerName); } else { writer = MethodWriter.write( managedCtClass, "public void %s(%s %s) {%n %s%n}", writerName, persistentField.getType().getName(), fieldName, typeDescriptor.buildWriteInterceptionBodyFragment(fieldName)); } if (enhancementContext.isCompositeClass(managedCtClass)) { writer.insertBefore( String.format( "if (%s != null) { %<s.callOwner(\".%s\"); }%n", EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME, fieldName)); } else if (enhancementContext.doDirtyCheckingInline(managedCtClass)) { writer.insertBefore( typeDescriptor.buildInLineDirtyCheckingBodyFragment( enhancementContext, persistentField)); } handleCompositeField(managedCtClass, persistentField, writer); if (enhancementContext.doBiDirectionalAssociationManagement(persistentField)) { handleBiDirectionalAssociation(managedCtClass, persistentField, writer); } return writer; } catch (CannotCompileException cce) { final String msg = String.format( "Could not enhance entity class [%s] to add field writer method [%s]", managedCtClass.getName(), writerName); throw new EnhancementException(msg, cce); } catch (NotFoundException nfe) { final String msg = String.format( "Could not enhance entity class [%s] to add field writer method [%s]", managedCtClass.getName(), writerName); throw new EnhancementException(msg, nfe); } } private void handleBiDirectionalAssociation( CtClass managedCtClass, CtField persistentField, CtMethod fieldWriter) throws NotFoundException, CannotCompileException { if (!isPossibleBiDirectionalAssociation(persistentField)) { return; } final CtClass targetEntity = getTargetEntityClass(persistentField); if (targetEntity == null) { log.debugf( "Could not find type of bi-directional association for field [%s#%s]", managedCtClass.getName(), persistentField.getName()); return; } final String mappedBy = getMappedBy(persistentField, targetEntity); if (mappedBy.isEmpty()) { log.warnf( "Could not find bi-directional association for field [%s#%s]", managedCtClass.getName(), persistentField.getName()); return; } // create a temporary getter and setter on the target entity to be able to compile our code final String mappedByGetterName = EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + mappedBy; final String mappedBySetterName = EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + mappedBy; MethodWriter.addGetter(targetEntity, mappedBy, mappedByGetterName); MethodWriter.addSetter(targetEntity, mappedBy, mappedBySetterName); if (persistentField.hasAnnotation(OneToOne.class)) { // only unset when $1 != null to avoid recursion fieldWriter.insertBefore( String.format( "if ($0.%s != null && $1 != null) $0.%<s.%s(null);%n", persistentField.getName(), mappedBySetterName)); fieldWriter.insertAfter( String.format( "if ($1 != null && $1.%s() != $0) $1.%s($0);%n", mappedByGetterName, mappedBySetterName)); } if (persistentField.hasAnnotation(OneToMany.class)) { // only remove elements not in the new collection or else we would loose those elements // don't use iterator to avoid ConcurrentModException fieldWriter.insertBefore( String.format( "if ($0.%s != null) { Object[] array = $0.%<s.toArray(); for (int i = 0; i < array.length; i++) { %s target = (%<s) array[i]; if ($1 == null || !$1.contains(target)) target.%s(null); } }%n", persistentField.getName(), targetEntity.getName(), mappedBySetterName)); fieldWriter.insertAfter( String.format( "if ($1 != null) { Object[] array = $1.toArray(); for (int i = 0; i < array.length; i++) { %s target = (%<s) array[i]; if (target.%s() != $0) target.%s((%s)$0); } }%n", targetEntity.getName(), mappedByGetterName, mappedBySetterName, managedCtClass.getName())); } if (persistentField.hasAnnotation(ManyToOne.class)) { fieldWriter.insertBefore( String.format( "if ($0.%1$s != null && $0.%1$s.%2$s() != null) $0.%1$s.%2$s().remove($0);%n", persistentField.getName(), mappedByGetterName)); // check .contains($0) to avoid double inserts (but preventing duplicates) fieldWriter.insertAfter( String.format( "if ($1 != null) { java.util.Collection c = $1.%s(); if (c != null && !c.contains($0)) c.add($0); }%n", mappedByGetterName)); } if (persistentField.hasAnnotation(ManyToMany.class)) { fieldWriter.insertBefore( String.format( "if ($0.%s != null) { Object[] array = $0.%<s.toArray(); for (int i = 0; i < array.length; i++) { %s target = (%<s) array[i]; if ($1 == null || !$1.contains(target)) target.%s().remove($0); } }%n", persistentField.getName(), targetEntity.getName(), mappedByGetterName)); fieldWriter.insertAfter( String.format( "if ($1 != null) { Object[] array = $1.toArray(); for (int i = 0; i < array.length; i++) { %s target = (%<s) array[i]; java.util.Collection c = target.%s(); if ( c != $0 && c != null) c.add($0); } }%n", targetEntity.getName(), mappedByGetterName)); } // implementation note: association management @OneToMany and @ManyToMay works for add() // operations but for remove() a snapshot of the collection is needed so we know what // associations to break. // another approach that could force that behavior would be to return // Collections.unmodifiableCollection() ... } private boolean isPossibleBiDirectionalAssociation(CtField persistentField) { return persistentField.hasAnnotation(OneToOne.class) || persistentField.hasAnnotation(OneToMany.class) || persistentField.hasAnnotation(ManyToOne.class) || persistentField.hasAnnotation(ManyToMany.class); } private String getMappedBy(CtField persistentField, CtClass targetEntity) { final String local = getMappedByFromAnnotation(persistentField); return local.isEmpty() ? getMappedByFromTargetEntity(persistentField, targetEntity) : local; } private String getMappedByFromAnnotation(CtField persistentField) { try { if (persistentField.hasAnnotation(OneToOne.class)) { return ((OneToOne) persistentField.getAnnotation(OneToOne.class)).mappedBy(); } if (persistentField.hasAnnotation(OneToMany.class)) { return ((OneToMany) persistentField.getAnnotation(OneToMany.class)).mappedBy(); } // For @ManyToOne associations, mappedBy must come from the @OneToMany side of the association if (persistentField.hasAnnotation(ManyToMany.class)) { return ((ManyToMany) persistentField.getAnnotation(ManyToMany.class)).mappedBy(); } } catch (ClassNotFoundException ignore) { } return ""; } private String getMappedByFromTargetEntity(CtField persistentField, CtClass targetEntity) { // get mappedBy value by searching in the fields of the target entity class for (CtField f : targetEntity.getDeclaredFields()) { if (enhancementContext.isPersistentField(f) && getMappedByFromAnnotation(f).equals(persistentField.getName())) { log.debugf( "mappedBy association for field [%s:%s] is [%s:%s]", persistentField.getDeclaringClass().getName(), persistentField.getName(), targetEntity.getName(), f.getName()); return f.getName(); } } return ""; } private CtClass getTargetEntityClass(CtField persistentField) throws NotFoundException { // get targetEntity defined in the annotation try { Class<?> targetClass = null; if (persistentField.hasAnnotation(OneToOne.class)) { targetClass = ((OneToOne) persistentField.getAnnotation(OneToOne.class)).targetEntity(); } if (persistentField.hasAnnotation(OneToMany.class)) { targetClass = ((OneToMany) persistentField.getAnnotation(OneToMany.class)).targetEntity(); } if (persistentField.hasAnnotation(ManyToOne.class)) { targetClass = ((ManyToOne) persistentField.getAnnotation(ManyToOne.class)).targetEntity(); } if (persistentField.hasAnnotation(ManyToMany.class)) { targetClass = ((ManyToMany) persistentField.getAnnotation(ManyToMany.class)).targetEntity(); } if (targetClass != null && targetClass != void.class) { return classPool.get(targetClass.getName()); } } catch (ClassNotFoundException ignore) { } // infer targetEntity from generic type signature if (persistentField.hasAnnotation(OneToOne.class) || persistentField.hasAnnotation(ManyToOne.class)) { return persistentField.getType(); } if (persistentField.hasAnnotation(OneToMany.class) || persistentField.hasAnnotation(ManyToMany.class)) { try { final SignatureAttribute.TypeArgument target = ((SignatureAttribute.ClassType) SignatureAttribute.toFieldSignature(persistentField.getGenericSignature())) .getTypeArguments()[0]; return persistentField.getDeclaringClass().getClassPool().get(target.toString()); } catch (BadBytecode ignore) { } } return null; } private void handleCompositeField( CtClass managedCtClass, CtField persistentField, CtMethod fieldWriter) throws NotFoundException, CannotCompileException { if (!persistentField.hasAnnotation(Embedded.class)) { return; } // make sure to add the CompositeOwner interface managedCtClass.addInterface(classPool.get(CompositeOwner.class.getName())); if (enhancementContext.isCompositeClass(managedCtClass)) { // if a composite have a embedded field we need to implement the TRACKER_CHANGER_NAME method // as well MethodWriter.write( managedCtClass, "" + "public void %1$s(String name) {%n" + " if (%2$s != null) { %2$s.callOwner(\".\" + name) ; }%n}", EnhancerConstants.TRACKER_CHANGER_NAME, EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME); } // cleanup previous owner fieldWriter.insertBefore( String.format( "" + "if (%1$s != null) { ((%2$s) %1$s).%3$s(\"%1$s\"); }%n", persistentField.getName(), CompositeTracker.class.getName(), EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER)); // trigger track changes fieldWriter.insertAfter( String.format( "" + "((%2$s) %1$s).%4$s(\"%1$s\", (%3$s) this);%n" + "%5$s(\"%1$s\");", persistentField.getName(), CompositeTracker.class.getName(), CompositeOwner.class.getName(), EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER, EnhancerConstants.TRACKER_CHANGER_NAME)); } protected void enhanceAttributesAccess( CtClass managedCtClass, IdentityHashMap<String, PersistentAttributeAccessMethods> attributeDescriptorMap) { final ConstPool constPool = managedCtClass.getClassFile().getConstPool(); for (Object oMethod : managedCtClass.getClassFile().getMethods()) { final MethodInfo methodInfo = (MethodInfo) oMethod; final String methodName = methodInfo.getName(); // skip methods added by enhancement and abstract methods (methods without any code) if (methodName.startsWith("$$_hibernate_") || methodInfo.getCodeAttribute() == null) { continue; } try { final CodeIterator itr = methodInfo.getCodeAttribute().iterator(); while (itr.hasNext()) { final int index = itr.next(); final int op = itr.byteAt(index); if (op != Opcode.PUTFIELD && op != Opcode.GETFIELD) { continue; } final String fieldName = constPool.getFieldrefName(itr.u16bitAt(index + 1)); final PersistentAttributeAccessMethods attributeMethods = attributeDescriptorMap.get(fieldName); // its not a field we have enhanced for interception, so skip it if (attributeMethods == null) { continue; } // System.out.printf( "Transforming access to field [%s] from method [%s]%n", fieldName, // methodName ); log.debugf("Transforming access to field [%s] from method [%s]", fieldName, methodName); if (op == Opcode.GETFIELD) { final int methodIndex = MethodWriter.addMethod(constPool, attributeMethods.getReader()); itr.writeByte(Opcode.INVOKESPECIAL, index); itr.write16bit(methodIndex, index + 1); } else { final int methodIndex = MethodWriter.addMethod(constPool, attributeMethods.getWriter()); itr.writeByte(Opcode.INVOKESPECIAL, index); itr.write16bit(methodIndex, index + 1); } } methodInfo.getCodeAttribute().setAttribute(MapMaker.make(classPool, methodInfo)); } catch (BadBytecode bb) { final String msg = String.format( "Unable to perform field access transformation in method [%s]", methodName); throw new EnhancementException(msg, bb); } } } private static class PersistentAttributeAccessMethods { private final CtMethod reader; private final CtMethod writer; private PersistentAttributeAccessMethods(CtMethod reader, CtMethod writer) { this.reader = reader; this.writer = writer; } private CtMethod getReader() { return reader; } private CtMethod getWriter() { return writer; } } /** * Replace access to fields of entities (for example, entity.field) with a call to the enhanced * getter / setter (in this example, entity.$$_hibernate_read_field()). It's assumed that the * target entity is enhanced as well. * * @param managedCtClass Class to enhance */ public void enhanceFieldAccess(CtClass managedCtClass) { final ConstPool constPool = managedCtClass.getClassFile().getConstPool(); for (Object oMethod : managedCtClass.getClassFile().getMethods()) { final MethodInfo methodInfo = (MethodInfo) oMethod; final String methodName = methodInfo.getName(); // skip methods added by enhancement and abstract methods (methods without any code) if (methodName.startsWith("$$_hibernate_") || methodInfo.getCodeAttribute() == null) { continue; } try { final CodeIterator itr = methodInfo.getCodeAttribute().iterator(); while (itr.hasNext()) { int index = itr.next(); int op = itr.byteAt(index); if (op != Opcode.PUTFIELD && op != Opcode.GETFIELD) { continue; } String fieldName = constPool.getFieldrefName(itr.u16bitAt(index + 1)); String fieldClassName = constPool.getClassInfo(constPool.getFieldrefClass(itr.u16bitAt(index + 1))); CtClass targetCtClass = this.classPool.getCtClass(fieldClassName); if (!enhancementContext.isEntityClass(targetCtClass) && !enhancementContext.isCompositeClass(targetCtClass)) { continue; } if (targetCtClass == managedCtClass || !enhancementContext.isPersistentField(targetCtClass.getField(fieldName)) || "this$0".equals(fieldName)) { continue; } log.debugf("Transforming access to field [%s] from method [%s]", fieldName, methodName); if (op == Opcode.GETFIELD) { int fieldReaderMethodIndex = constPool.addMethodrefInfo( constPool.addClassInfo(fieldClassName), EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + fieldName, "()" + constPool.getFieldrefType(itr.u16bitAt(index + 1))); itr.writeByte(Opcode.INVOKEVIRTUAL, index); itr.write16bit(fieldReaderMethodIndex, index + 1); } else { int fieldWriterMethodIndex = constPool.addMethodrefInfo( constPool.addClassInfo(fieldClassName), EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + fieldName, "(" + constPool.getFieldrefType(itr.u16bitAt(index + 1)) + ")V"); itr.writeByte(Opcode.INVOKEVIRTUAL, index); itr.write16bit(fieldWriterMethodIndex, index + 1); } } methodInfo.getCodeAttribute().setAttribute(MapMaker.make(classPool, methodInfo)); } catch (BadBytecode bb) { final String msg = String.format( "Unable to perform field access transformation in method [%s]", methodName); throw new EnhancementException(msg, bb); } catch (NotFoundException nfe) { final String msg = String.format( "Unable to perform field access transformation in method [%s]", methodName); throw new EnhancementException(msg, nfe); } } } }
/** * The isolation delegate for JDBC {@link Connection} based transactions * * @author Steve Ebersole */ public class JdbcIsolationDelegate implements IsolationDelegate { private static final CoreMessageLogger LOG = CoreLogging.messageLogger(JdbcIsolationDelegate.class); private final TransactionCoordinator transactionCoordinator; public JdbcIsolationDelegate(TransactionCoordinator transactionCoordinator) { this.transactionCoordinator = transactionCoordinator; } protected JdbcConnectionAccess jdbcConnectionAccess() { return transactionCoordinator.getTransactionContext().getJdbcConnectionAccess(); } protected SqlExceptionHelper sqlExceptionHelper() { return transactionCoordinator .getJdbcCoordinator() .getLogicalConnection() .getJdbcServices() .getSqlExceptionHelper(); } @Override public <T> T delegateWork(WorkExecutorVisitable<T> work, boolean transacted) throws HibernateException { boolean wasAutoCommit = false; try { Connection connection = jdbcConnectionAccess().obtainConnection(); try { if (transacted) { if (connection.getAutoCommit()) { wasAutoCommit = true; connection.setAutoCommit(false); } } T result = work.accept(new WorkExecutor<T>(), connection); if (transacted) { connection.commit(); } return result; } catch (Exception e) { try { if (transacted && !connection.isClosed()) { connection.rollback(); } } catch (Exception ignore) { LOG.unableToRollbackConnection(ignore); } if (e instanceof HibernateException) { throw (HibernateException) e; } else if (e instanceof SQLException) { throw sqlExceptionHelper().convert((SQLException) e, "error performing isolated work"); } else { throw new HibernateException("error performing isolated work", e); } } finally { if (transacted && wasAutoCommit) { try { connection.setAutoCommit(true); } catch (Exception ignore) { LOG.trace("was unable to reset connection back to auto-commit"); } } try { jdbcConnectionAccess().releaseConnection(connection); } catch (Exception ignore) { LOG.unableToReleaseIsolatedConnection(ignore); } } } catch (SQLException sqle) { throw sqlExceptionHelper().convert(sqle, "unable to obtain isolated JDBC connection"); } } }
/** * Encapsulates the creation of FromElements and JoinSequences. * * @author josh */ public class FromElementFactory implements SqlTokenTypes { private static final CoreMessageLogger LOG = CoreLogging.messageLogger(FromElementFactory.class); private FromClause fromClause; private FromElement origin; private String path; private String classAlias; private String[] columns; private boolean implied; private boolean inElementsFunction; private boolean collection; private QueryableCollection queryableCollection; private CollectionType collectionType; /** Creates entity from elements. */ public FromElementFactory(FromClause fromClause, FromElement origin, String path) { this.fromClause = fromClause; this.origin = origin; this.path = path; collection = false; } /** Creates collection from elements. */ public FromElementFactory( FromClause fromClause, FromElement origin, String path, String classAlias, String[] columns, boolean implied) { this(fromClause, origin, path); this.classAlias = classAlias; this.columns = columns; this.implied = implied; collection = true; } FromElement addFromElement() throws SemanticException { final FromClause parentFromClause = fromClause.getParentFromClause(); if (parentFromClause != null) { // Look up class name using the first identifier in the path. final String pathAlias = PathHelper.getAlias(path); final FromElement parentFromElement = parentFromClause.getFromElement(pathAlias); if (parentFromElement != null) { return createFromElementInSubselect(path, pathAlias, parentFromElement, classAlias); } } final EntityPersister entityPersister = fromClause.getSessionFactoryHelper().requireClassPersister(path); final FromElement elem = createAndAddFromElement( path, classAlias, entityPersister, (EntityType) ((Queryable) entityPersister).getType(), null); // Add to the query spaces. fromClause.getWalker().addQuerySpaces(entityPersister.getQuerySpaces()); return elem; } private FromElement createFromElementInSubselect( String path, String pathAlias, FromElement parentFromElement, String classAlias) throws SemanticException { LOG.debugf("createFromElementInSubselect() : path = %s", path); // Create an DotNode AST for the path and resolve it. FromElement fromElement = evaluateFromElementPath(path, classAlias); EntityPersister entityPersister = fromElement.getEntityPersister(); // If the first identifier in the path refers to the class alias (not the class name), then this // is a correlated subselect. If it's a correlated sub-select, use the existing table alias. // Otherwise // generate a new one. String tableAlias = null; boolean correlatedSubselect = pathAlias.equals(parentFromElement.getClassAlias()); if (correlatedSubselect) { tableAlias = fromElement.getTableAlias(); } else { tableAlias = null; } // If the from element isn't in the same clause, create a new from element. if (fromElement.getFromClause() != fromClause) { LOG.debug("createFromElementInSubselect() : creating a new FROM element..."); fromElement = createFromElement(entityPersister); initializeAndAddFromElement( fromElement, path, classAlias, entityPersister, (EntityType) ((Queryable) entityPersister).getType(), tableAlias); } LOG.debugf("createFromElementInSubselect() : %s -> %s", path, fromElement); return fromElement; } private FromElement evaluateFromElementPath(String path, String classAlias) throws SemanticException { ASTFactory factory = fromClause.getASTFactory(); FromReferenceNode pathNode = (FromReferenceNode) PathHelper.parsePath(path, factory); pathNode.recursiveResolve( // This is the root level node. FromReferenceNode.ROOT_LEVEL, // Generate an explicit from clause at the root. false, classAlias, null); if (pathNode.getImpliedJoin() != null) { return pathNode.getImpliedJoin(); } return pathNode.getFromElement(); } FromElement createCollectionElementsJoin( QueryableCollection queryableCollection, String collectionName) throws SemanticException { JoinSequence collectionJoinSequence = fromClause .getSessionFactoryHelper() .createCollectionJoinSequence(queryableCollection, collectionName); this.queryableCollection = queryableCollection; return createCollectionJoin(collectionJoinSequence, null); } public FromElement createCollection( QueryableCollection queryableCollection, String role, JoinType joinType, boolean fetchFlag, boolean indexed) throws SemanticException { if (!collection) { throw new IllegalStateException("FromElementFactory not initialized for collections!"); } this.inElementsFunction = indexed; FromElement elem; this.queryableCollection = queryableCollection; collectionType = queryableCollection.getCollectionType(); String roleAlias = fromClause.getAliasGenerator().createName(role); // Correlated subqueries create 'special' implied from nodes // because correlated subselects can't use an ANSI-style join boolean explicitSubqueryFromElement = fromClause.isSubQuery() && !implied; if (explicitSubqueryFromElement) { String pathRoot = StringHelper.root(path); FromElement origin = fromClause.getFromElement(pathRoot); if (origin == null || origin.getFromClause() != fromClause) { implied = true; } } // super-duper-classic-parser-regression-testing-mojo-magic... if (explicitSubqueryFromElement && PathSeparatorNode.useThetaStyleImplicitJoins) { implied = true; } Type elementType = queryableCollection.getElementType(); if (elementType.isEntityType()) { // A collection of entities... elem = createEntityAssociation(role, roleAlias, joinType); } else if (elementType.isComponentType()) { // A collection of components... JoinSequence joinSequence = createJoinSequence(roleAlias, joinType); elem = createCollectionJoin(joinSequence, roleAlias); } else { // A collection of scalar elements... JoinSequence joinSequence = createJoinSequence(roleAlias, joinType); elem = createCollectionJoin(joinSequence, roleAlias); } elem.setRole(role); elem.setQueryableCollection(queryableCollection); // Don't include sub-classes for implied collection joins or subquery joins. if (implied) { elem.setIncludeSubclasses(false); } if (explicitSubqueryFromElement) { // Treat explict from elements in sub-queries properly. elem.setInProjectionList(true); } if (fetchFlag) { elem.setFetch(true); } return elem; } public FromElement createEntityJoin( String entityClass, String tableAlias, JoinSequence joinSequence, boolean fetchFlag, boolean inFrom, EntityType type, String role, String joinPath) throws SemanticException { FromElement elem = createJoin(entityClass, tableAlias, joinSequence, type, false); elem.setFetch(fetchFlag); if (joinPath != null) { elem.applyTreatAsDeclarations(fromClause.getWalker().getTreatAsDeclarationsByPath(joinPath)); } EntityPersister entityPersister = elem.getEntityPersister(); int numberOfTables = entityPersister.getQuerySpaces().length; if (numberOfTables > 1 && implied && !elem.useFromFragment()) { LOG.debug("createEntityJoin() : Implied multi-table entity join"); elem.setUseFromFragment(true); } // If this is an implied join in a FROM clause, then use ANSI-style joining, and set the // flag on the FromElement that indicates that it was implied in the FROM clause itself. if (implied && inFrom) { joinSequence.setUseThetaStyle(false); elem.setUseFromFragment(true); elem.setImpliedInFromClause(true); } if (elem.getWalker().isSubQuery()) { // two conditions where we need to transform this to a theta-join syntax: // 1) 'elem' is the "root from-element" in correlated subqueries // 2) The DotNode.useThetaStyleImplicitJoins has been set to true // and 'elem' represents an implicit join if (elem.getFromClause() != elem.getOrigin().getFromClause() || // ( implied && DotNode.useThetaStyleImplicitJoins ) ) { PathSeparatorNode.useThetaStyleImplicitJoins) { // the "root from-element" in correlated subqueries do need this piece elem.setType(FROM_FRAGMENT); joinSequence.setUseThetaStyle(true); elem.setUseFromFragment(false); } } elem.setRole(role); return elem; } public FromElement createComponentJoin(ComponentType type) { // need to create a "place holder" from-element that can store the component/alias for this // component join return new ComponentJoin(fromClause, origin, classAlias, path, type); } FromElement createElementJoin(QueryableCollection queryableCollection) throws SemanticException { FromElement elem; implied = true; // TODO: always true for now, but not if we later decide to support elements() in the // from clause inElementsFunction = true; Type elementType = queryableCollection.getElementType(); if (!elementType.isEntityType()) { throw new IllegalArgumentException( "Cannot create element join for a collection of non-entities!"); } this.queryableCollection = queryableCollection; SessionFactoryHelper sfh = fromClause.getSessionFactoryHelper(); FromElement destination = null; String tableAlias = null; EntityPersister entityPersister = queryableCollection.getElementPersister(); tableAlias = fromClause.getAliasGenerator().createName(entityPersister.getEntityName()); String associatedEntityName = entityPersister.getEntityName(); EntityPersister targetEntityPersister = sfh.requireClassPersister(associatedEntityName); // Create the FROM element for the target (the elements of the collection). destination = createAndAddFromElement( associatedEntityName, classAlias, targetEntityPersister, (EntityType) queryableCollection.getElementType(), tableAlias); // If the join is implied, then don't include sub-classes on the element. if (implied) { destination.setIncludeSubclasses(false); } fromClause.addCollectionJoinFromElementByPath(path, destination); // origin.addDestination(destination); // Add the query spaces. fromClause.getWalker().addQuerySpaces(entityPersister.getQuerySpaces()); CollectionType type = queryableCollection.getCollectionType(); String role = type.getRole(); String roleAlias = origin.getTableAlias(); String[] targetColumns = sfh.getCollectionElementColumns(role, roleAlias); AssociationType elementAssociationType = sfh.getElementAssociationType(type); // Create the join element under the from element. JoinType joinType = JoinType.INNER_JOIN; JoinSequence joinSequence = sfh.createJoinSequence( implied, elementAssociationType, tableAlias, joinType, targetColumns); elem = initializeJoin(path, destination, joinSequence, targetColumns, origin, false); elem.setUseFromFragment( true); // The associated entity is implied, but it must be included in the FROM. elem.setCollectionTableAlias(roleAlias); // The collection alias is the role. return elem; } private FromElement createCollectionJoin(JoinSequence collectionJoinSequence, String tableAlias) throws SemanticException { String text = queryableCollection.getTableName(); AST ast = createFromElement(text); FromElement destination = (FromElement) ast; Type elementType = queryableCollection.getElementType(); if (elementType.isCollectionType()) { throw new SemanticException("Collections of collections are not supported!"); } destination.initializeCollection(fromClause, classAlias, tableAlias); destination.setType(JOIN_FRAGMENT); // Tag this node as a JOIN. destination.setIncludeSubclasses(false); // Don't include subclasses in the join. destination.setCollectionJoin(true); // This is a clollection join. destination.setJoinSequence(collectionJoinSequence); destination.setOrigin(origin, false); destination.setCollectionTableAlias(tableAlias); // origin.addDestination( destination ); // This was the cause of HHH-242 // origin.setType( FROM_FRAGMENT ); // Set the parent node type so that the AST is properly // formed. origin.setText(""); // The destination node will have all the FROM text. origin.setCollectionJoin( true); // The parent node is a collection join too (voodoo - see JoinProcessor) fromClause.addCollectionJoinFromElementByPath(path, destination); fromClause.getWalker().addQuerySpaces(queryableCollection.getCollectionSpaces()); return destination; } private FromElement createEntityAssociation(String role, String roleAlias, JoinType joinType) throws SemanticException { FromElement elem; Queryable entityPersister = (Queryable) queryableCollection.getElementPersister(); String associatedEntityName = entityPersister.getEntityName(); // Get the class name of the associated entity. if (queryableCollection.isOneToMany()) { LOG.debugf( "createEntityAssociation() : One to many - path = %s role = %s associatedEntityName = %s", path, role, associatedEntityName); JoinSequence joinSequence = createJoinSequence(roleAlias, joinType); elem = createJoin( associatedEntityName, roleAlias, joinSequence, (EntityType) queryableCollection.getElementType(), false); } else { LOG.debugf( "createManyToMany() : path = %s role = %s associatedEntityName = %s", path, role, associatedEntityName); elem = createManyToMany( role, associatedEntityName, roleAlias, entityPersister, (EntityType) queryableCollection.getElementType(), joinType); fromClause.getWalker().addQuerySpaces(queryableCollection.getCollectionSpaces()); } elem.setCollectionTableAlias(roleAlias); return elem; } private FromElement createJoin( String entityClass, String tableAlias, JoinSequence joinSequence, EntityType type, boolean manyToMany) throws SemanticException { // origin, path, implied, columns, classAlias, EntityPersister entityPersister = fromClause.getSessionFactoryHelper().requireClassPersister(entityClass); FromElement destination = createAndAddFromElement(entityClass, classAlias, entityPersister, type, tableAlias); return initializeJoin(path, destination, joinSequence, getColumns(), origin, manyToMany); } private FromElement createManyToMany( String role, String associatedEntityName, String roleAlias, Queryable entityPersister, EntityType type, JoinType joinType) throws SemanticException { FromElement elem; SessionFactoryHelper sfh = fromClause.getSessionFactoryHelper(); if (inElementsFunction /*implied*/) { // For implied many-to-many, just add the end join. JoinSequence joinSequence = createJoinSequence(roleAlias, joinType); elem = createJoin(associatedEntityName, roleAlias, joinSequence, type, true); } else { // For an explicit many-to-many relationship, add a second join from the intermediate // (many-to-many) table to the destination table. Also, make sure that the from element's // idea of the destination is the destination table. String tableAlias = fromClause.getAliasGenerator().createName(entityPersister.getEntityName()); String[] secondJoinColumns = sfh.getCollectionElementColumns(role, roleAlias); // Add the second join, the one that ends in the destination table. JoinSequence joinSequence = createJoinSequence(roleAlias, joinType); joinSequence.addJoin( sfh.getElementAssociationType(collectionType), tableAlias, joinType, secondJoinColumns); elem = createJoin(associatedEntityName, tableAlias, joinSequence, type, false); elem.setUseFromFragment(true); } return elem; } private JoinSequence createJoinSequence(String roleAlias, JoinType joinType) { SessionFactoryHelper sessionFactoryHelper = fromClause.getSessionFactoryHelper(); String[] joinColumns = getColumns(); if (collectionType == null) { throw new IllegalStateException("collectionType is null!"); } return sessionFactoryHelper.createJoinSequence( implied, collectionType, roleAlias, joinType, joinColumns); } private FromElement createAndAddFromElement( String className, String classAlias, EntityPersister entityPersister, EntityType type, String tableAlias) { if (!(entityPersister instanceof Joinable)) { throw new IllegalArgumentException( "EntityPersister " + entityPersister + " does not implement Joinable!"); } FromElement element = createFromElement(entityPersister); initializeAndAddFromElement(element, className, classAlias, entityPersister, type, tableAlias); return element; } private void initializeAndAddFromElement( FromElement element, String className, String classAlias, EntityPersister entityPersister, EntityType type, String tableAlias) { if (tableAlias == null) { AliasGenerator aliasGenerator = fromClause.getAliasGenerator(); tableAlias = aliasGenerator.createName(entityPersister.getEntityName()); } element.initializeEntity(fromClause, className, entityPersister, type, classAlias, tableAlias); } private FromElement createFromElement(EntityPersister entityPersister) { Joinable joinable = (Joinable) entityPersister; String text = joinable.getTableName(); AST ast = createFromElement(text); FromElement element = (FromElement) ast; return element; } private AST createFromElement(String text) { AST ast = ASTUtil.create( fromClause.getASTFactory(), implied ? IMPLIED_FROM : FROM_FRAGMENT, // This causes the factory to instantiate the desired class. text); // Reset the node type, because the rest of the system is expecting FROM_FRAGMENT, all we wanted // was // for the factory to create the right sub-class. This might get reset again later on anyway to // make the // SQL generation simpler. ast.setType(FROM_FRAGMENT); return ast; } private FromElement initializeJoin( String path, FromElement destination, JoinSequence joinSequence, String[] columns, FromElement origin, boolean manyToMany) { destination.setType(JOIN_FRAGMENT); destination.setJoinSequence(joinSequence); destination.setColumns(columns); destination.setOrigin(origin, manyToMany); fromClause.addJoinByPathMap(path, destination); return destination; } private String[] getColumns() { if (columns == null) { throw new IllegalStateException("No foriegn key columns were supplied!"); } return columns; } }