/* * #! * Ontopia Engine * #- * Copyright (C) 2001 - 2013 The Ontopia Project * #- * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * !# */ package net.ontopia.persistence.proxy; import java.lang.reflect.Method; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import net.ontopia.persistence.query.sql.SQLValueIF; /** * INTERNAL: A field that represents the identity of instances of a * class. An identity field is a composite of one or more fields that * together represent the identity of objects.<p> */ public class IdentityFieldInfo implements FieldInfoIF { protected ClassInfoIF parent_cinfo; protected Class<?> parent_class; protected FieldInfoIF[] fields; protected int fields_length; protected String[] value_columns; protected int column_count; protected Method getter; protected Method setter; protected int sqlType = -1; public IdentityFieldInfo(ClassInfoIF parent_cinfo, FieldInfoIF[] identity_fields) { // Class information this.parent_cinfo = parent_cinfo; this.parent_class = parent_cinfo.getDescriptorClass(); // Field information this.fields = identity_fields; this.fields_length = fields.length; // Compute value columns this.value_columns = computeValueColumns(); this.column_count = value_columns.length; // Optimization: Hack to figure out if the field is a long field if (fields_length == 1 && fields[0] instanceof PrimitiveFieldInfo) this.sqlType = ((PrimitiveFieldInfo)fields[0]).getSQLType(); } /** * INTERNAL: Returns the underlying FieldInfoIFs that the identity * field spans. */ public FieldInfoIF[] getFields() { return fields; } public String getName() { throw new UnsupportedOperationException("This method should not be called for IdentityFieldInfo."); } public int getIndex() { throw new UnsupportedOperationException("This method should not be called for IdentityFieldInfo."); } public int getCardinality() { return FieldInfoIF.ONE_TO_ONE; } public boolean isReadOnly() { return true; } public boolean isIdentityField() { return true; } public boolean isCollectionField() { return false; } public boolean isPrimitiveField() { return false; } public boolean isReferenceField() { return false; } public boolean isAggregateField() { return false; } public ClassInfoIF getParentClassInfo() { return parent_cinfo; } public ClassInfoIF getValueClassInfo() { throw new UnsupportedOperationException("This method should not be called for IdentityFieldInfo."); } public Class<?> getValueClass() { throw new UnsupportedOperationException("This method should not be called for IdentityFieldInfo."); } public String getTable() { throw new UnsupportedOperationException("This method should not be called for IdentityFieldInfo."); } public int getColumnCount() { return column_count; } public String[] getValueColumns() { return value_columns; } protected String[] computeValueColumns() { // Collect column names from children List<String> names = new ArrayList<String>(); aggregateColumnNames(names); // Morph into a string array String[] _names = new String[names.size()]; names.toArray(_names); return _names; } protected void aggregateColumnNames(List<String> columns) { for (int i=0; i < fields_length; i++) { columns.addAll(Arrays.asList(fields[i].getValueColumns())); } } private final IdentityIF getIdentity(Object value) { if (value instanceof PersistentIF) return ((PersistentIF)value)._p_getIdentity(); else return (IdentityIF)value; } //! // WARNING: This is actually incorrect, since we should be getting //! // the identity of the value object, not that parent object as we //! // do here. //! //! public Object getValue(Object object) throws Exception { //! return ((PersistentIF)object)._p_getIdentity(); //! } //! //! public void setValue(Object object, Object value) throws Exception { //! ((PersistentIF)object)._p_setIdentity((IdentityIF)value); //! } public Object getValue(Object object) throws Exception { throw new UnsupportedOperationException("This method should not be called for IdentityFieldInfo."); } public void setValue(Object object, Object value) throws Exception { throw new UnsupportedOperationException("This method should not be called for IdentityFieldInfo."); } public String getJoinTable() { throw new UnsupportedOperationException("This method should not be called for IdentityFieldInfo."); } public String[] getJoinKeys() { throw new UnsupportedOperationException("This method should not be called for IdentityFieldInfo."); } public String[] getManyKeys() { throw new UnsupportedOperationException("This method should not be called for IdentityFieldInfo."); } /// --- FieldHandlerIF implementation /** * INTERNAL: Loads from its containing fields an IdentityIF with the * field values as key. */ @Override public Object load(AccessRegistrarIF registrar, TicketIF ticket, ResultSet rs, int rsindex, boolean direct) throws SQLException { // FIXME: Should we open up for the possibility of identity fields // should being null? Then reference field handlers can just // delegate to identity field handlers. // Optimization: primitive long field if (sqlType == Types.BIGINT) { long value = rs.getLong(rsindex); if (rs.wasNull()) return null; else return registrar.createIdentity(parent_class, value); } else { // If first key is null, no object is referenced. Object first_key = fields[0].load(registrar, ticket, rs, rsindex, direct); if (first_key == null) return null; if (fields_length == 1) { // If the identity only consists of a single key component return // an atomic identity instance. //! return new AtomicIdentity(parent_class, first_key); return registrar.createIdentity(parent_class, first_key); } else { // Initialize key array Object[] keys = new Object[fields_length]; // Insert first key keys[0] = first_key; // Loop over key fields and collect field values for (int i=1; i < fields_length; i++) { rsindex += fields[i-1].getColumnCount(); keys[i] = fields[i].load(registrar, ticket, rs, rsindex, direct); } // Return identity return registrar.createIdentity(parent_class, keys); } } } /** * INTERNAL: Binds the identity keys to the containing fields. */ @Override public void bind(Object value, PreparedStatement stm, int stmt_index) throws SQLException { // Get the identity IdentityIF identity = getIdentity(value); if (identity == null) { // All keys bind null int offset = stmt_index; for (int i=0; i < fields_length; i++) { fields[i].bind(null, stm, stmt_index); offset += fields[i].getColumnCount(); } } else { // Bind key value int offset = stmt_index; for (int i=0; i < fields_length; i++) { fields[i].bind(identity.getKey(i), stm, stmt_index); offset += fields[i].getColumnCount(); } } } @Override public void retrieveFieldValues(Object value, List<Object> field_values) { // Get the identity keys IdentityIF identity = getIdentity(value); if (identity == null) { // Use null for (int i=0; i < fields_length; i++) { fields[i].retrieveFieldValues(null, field_values); } } else { // Use key value for (int i=0; i < fields_length; i++) { fields[i].retrieveFieldValues(identity.getKey(i), field_values); } } } @Override public void retrieveSQLValues(Object value, List<SQLValueIF> sql_values) { // Get the identity keys IdentityIF identity = getIdentity(value); if (identity == null) { // Use null for (int i=0; i < fields_length; i++) { fields[i].retrieveSQLValues(null, sql_values); } } else { // Use key value for (int i=0; i < fields_length; i++) { fields[i].retrieveSQLValues(identity.getKey(i), sql_values); } } } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("<IdentityFieldInfo ["); for (int i=0; i < fields_length; i++) { sb.append(fields[i].toString()); } sb.append("]>"); return sb.toString(); } /// -- Misc /** * INTERNAL: Returns the underlying FieldInfoIF instances. */ public FieldInfoIF[] getFieldInfos() { return fields; } }