/*
* #!
* 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.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import net.ontopia.persistence.query.sql.SQLValueIF;
/**
* INTERNAL: A field that references an aggregate class.<p>
*
* An aggregate field is a composite of one or more fields that
* together represent the fields of instances of the aggregate value
* class.<p>
*/
public class AggregateFieldInfo extends AbstractFieldInfo {
protected ClassInfoIF value_cinfo;
protected FieldInfoIF[] fields;
protected String[] value_columns;
protected int column_count;
public AggregateFieldInfo(ClassInfoIF parent_cinfo, FieldDescriptor field, int index) {
super(parent_cinfo, field, index);
// Class information
this.value_cinfo = parent_cinfo.getMapping().getClassInfo(field.getValueClass());
// Compile field information
// fields = FieldUtils.compileFieldInfo(field.getValueClassDescriptor().getValueFields());
fields = value_cinfo.getOne2OneFieldInfos();
// Compute value columns
value_columns = computeValueColumns();
column_count = value_columns.length;
}
public ClassInfoIF getValueClassInfo() {
return value_cinfo;
}
public int getColumnCount() {
return column_count;
}
public boolean isIdentityField() {
return false;
}
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 (FieldInfoIF _field : fields) {
columns.addAll(Arrays.asList(_field.getValueColumns()));
}
}
protected Object readAggregateObject(AccessRegistrarIF registrar, TicketIF ticket, ResultSet rs, int rsindex, boolean direct) throws SQLException {
// Instanciate new aggregate object
Object aggregate_object = null;
// Loop over aggregate fields and collect field values
int width = getColumnCount();
for (int i=0; i < width; i++) {
// Load field value
FieldInfoIF finfo = fields[i];
Object value = finfo.load(registrar, ticket, rs, rsindex, direct);
// FIXME: If all aggregate columns contains nulls the aggregate
// object will not be created. Other policies might also be
// useful. It could perhaps be specified in the mapping file.
// Only set value if field is not null
if (value != null) {
try {
if (aggregate_object == null) {
// FIXME: Should use a factory to create instances instead
// e.g. AggregateFactoryIF.create(FieldInfoIF)
aggregate_object = field.getValueClass().newInstance();
}
// Set value
finfo.setValue(aggregate_object, value);
} catch (Exception e) {
throw new PersistenceRuntimeException(e);
}
}
// Increment column index
rsindex += finfo.getColumnCount();
}
return aggregate_object;
}
/// --- FieldHandlerIF implementation
/**
* INTERNAL: Loads from its containing fields an aggregate object.
*/
@Override
public Object load(AccessRegistrarIF registrar, TicketIF ticket, ResultSet rs, int rsindex, boolean direct) throws SQLException {
// Read aggregate object
return readAggregateObject(registrar, ticket, rs, rsindex, direct);
}
@Override
public void bind(Object value, PreparedStatement stm, int stmt_index) throws SQLException {
// value is an aggregate object
// Let each aggregate field bind each key value
int offset = stmt_index;
for (int i=0; i < fields.length; i++) {
FieldInfoIF finfo = fields[i];
// FIXME: Could do the getValue and bind in one operation:
// FieldInfoIF.bindObject(o,stm,offset); Alternatively the
// getValue method should be moved somewhere else, or at least
// out of the FieldInfoIF interface.
Object field_value;
if (value == null)
field_value = null;
else {
try {
field_value = finfo.getValue(value);
} catch (Exception e) {
throw new PersistenceRuntimeException(e);
}
}
finfo.bind(field_value, stm, offset);
offset += finfo.getColumnCount();
}
}
@Override
public void retrieveFieldValues(Object value, List<Object> field_values) {
for (int i=0; i < fields.length; i++) {
try {
if (value == null)
fields[i].retrieveFieldValues(null, field_values);
else
fields[i].retrieveFieldValues(fields[i].getValue(value), field_values);
} catch (Exception e) {
throw new PersistenceRuntimeException(e);
}
}
}
@Override
public void retrieveSQLValues(Object value, List<SQLValueIF> sql_values) {
for (int i=0; i < fields.length; i++) {
try {
if (value == null)
fields[i].retrieveSQLValues(null, sql_values);
else
fields[i].retrieveSQLValues(fields[i].getValue(value), sql_values);
} catch (Exception e) {
throw new PersistenceRuntimeException(e);
}
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("<AggregateFieldInfo " + field.getName() + " [");
for (int i=0; i < fields.length; i++) {
sb.append(fields[i].toString());
}
sb.append("]>");
return sb.toString();
}
}