org.protempa.backend.dsb.relationaldb.EntitySpec Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of protempa-dsb-relationaldb Show documentation
Show all versions of protempa-dsb-relationaldb Show documentation
Implements support for retrieving data from relational
databases.
The newest version!
/*
* #%L
* Protempa Commons Backend Provider
* %%
* Copyright (C) 2012 - 2013 Emory University
* %%
* 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.
* #L%
*/
package org.protempa.backend.dsb.relationaldb;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.logging.Level;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.protempa.ProtempaUtil;
import org.protempa.proposition.value.Granularity;
import org.protempa.proposition.value.Unit;
import org.protempa.proposition.value.ValueType;
/**
* Defines a mapping from propositions to entities in a relational database.
*
* @author Andrew Post
*/
public final class EntitySpec implements Serializable {
private static final long serialVersionUID = -1935588032831088001L;
private static final PropertySpec[] EMPTY_PROPERTY_SPEC_ARRAY
= new PropertySpec[0];
private static final ReferenceSpec[] EMPTY_REFERENCE_SPEC_ARRAY
= new ReferenceSpec[0];
private static final ColumnSpec[] EMPTY_COLUMN_SPEC_ARRAY
= new ColumnSpec[0];
private final String name;
private final String description;
private final String[] propositionIds;
private final boolean unique;
private final ColumnSpec baseSpec;
private final ColumnSpec[] uniqueIdSpecs;
private final ColumnSpec startTimeOrTimestampSpec;
private final ColumnSpec finishTimeSpec;
private final PropertySpec[] propertySpecs;
private final ReferenceSpec[] referenceSpecs;
private final Map codeToPropIdMap;
private final ColumnSpec codeSpec;
private final ColumnSpec[] constraintSpecs;
private final ValueType valueType;
private final ColumnSpec valueSpec;
private final Granularity granularity;
private final JDBCPositionFormat positionParser;
private final Unit partitionBy;
private final int[] maxWidths;
private final ColumnSpec createDateSpec;
private final ColumnSpec updateDateSpec;
private final ColumnSpec deleteDateSpec;
public EntitySpec(String name,
String description,
String[] propositionIds,
boolean unique,
ColumnSpec baseSpec,
ColumnSpec[] uniqueIdSpecs,
ColumnSpec startTimeOrTimestampSpec,
ColumnSpec finishTimeSpec,
PropertySpec[] propertySpecs,
ReferenceSpec[] referenceSpecs,
Map codeToPropIdMap,
ColumnSpec codeSpec,
ColumnSpec[] constraintSpecs,
ColumnSpec valueSpec,
ValueType valueType,
Granularity granularity,
JDBCPositionFormat positionParser,
Unit partitionBy) {
this(name, description, propositionIds, unique, baseSpec,
uniqueIdSpecs, startTimeOrTimestampSpec, finishTimeSpec,
propertySpecs, referenceSpecs, codeToPropIdMap, codeSpec,
constraintSpecs, valueSpec, valueType, granularity,
positionParser, partitionBy, null);
}
public EntitySpec(String name,
String description,
String[] propositionIds,
boolean unique,
ColumnSpec baseSpec,
ColumnSpec[] uniqueIdSpecs,
ColumnSpec startTimeOrTimestampSpec,
ColumnSpec finishTimeSpec,
PropertySpec[] propertySpecs,
ReferenceSpec[] referenceSpecs,
Map codeToPropIdMap,
ColumnSpec codeSpec,
ColumnSpec[] constraintSpecs,
ColumnSpec valueSpec,
ValueType valueType,
Granularity granularity,
JDBCPositionFormat positionParser,
Unit partitionBy,
ColumnSpec createDateSpec,
ColumnSpec updateDateSpec,
ColumnSpec deleteDateSpec) {
this(name, description, propositionIds, unique, baseSpec,
uniqueIdSpecs, startTimeOrTimestampSpec, finishTimeSpec,
propertySpecs, referenceSpecs, codeToPropIdMap, codeSpec,
constraintSpecs, valueSpec, valueType, granularity,
positionParser, partitionBy, null,
createDateSpec, updateDateSpec, deleteDateSpec);
}
public EntitySpec(String name,
String description,
String[] propositionIds,
boolean unique,
ColumnSpec baseSpec,
ColumnSpec[] uniqueIdSpecs,
ColumnSpec startTimeOrTimestampSpec,
ColumnSpec finishTimeSpec,
PropertySpec[] propertySpecs,
ReferenceSpec[] referenceSpecs,
Map codeToPropIdMap,
ColumnSpec codeSpec,
ColumnSpec[] constraintSpecs,
ColumnSpec valueSpec,
ValueType valueType,
Granularity granularity,
JDBCPositionFormat positionParser,
Unit partitionBy,
int[] maxWidths) {
this(name, description, propositionIds, unique, baseSpec,
uniqueIdSpecs, startTimeOrTimestampSpec, finishTimeSpec,
propertySpecs, referenceSpecs, codeToPropIdMap, codeSpec,
constraintSpecs, valueSpec, valueType, granularity,
positionParser, partitionBy, null, null, null, null);
}
/**
* Creates an entity spec instance.
*
* @param name a {@link String}. Cannot be null.
* @param description a {@link String}. The constructor replaces a
* null argument with a {@link String} of length zero.
* @param propositionIds the proposition id {@link String[]}s to which this
* entity spec applies. Cannot contain null values. These
* propositions must all have the same set of properties.
* @param unique true if every row in the database table
* specified by the baseSpec argument contains a unique
* instance of this entity, false otherwise.
* @param baseSpec a {@link ColumnSpec} representing the path through the
* database from the key's main table to this entity's main table.
* @param uniqueIdSpec a {@link ColumnSpec[]} representing the paths through
* the database from this entity's main table to the tables and columns that
* together form an unique identifier for this entity. The columns
* comprising the unique identifier cannot have null values with one
* exception: if the column is also used for the PROTEMPA keyId, then it can
* have a null value (because records with a null keyId are discarded with
* just a logged warning).
* @param startTimeOrTimestampSpec a {@link ColumnSpec} representing the
* path through the database from this entity's main table to the table and
* column where the entity's start time (or timestamp, if no finish time is
* defined) is located, or null if this entity has no start
* time or timestamp.
* @param finishTimeSpec a {@link ColumnSpec} representing the path through
* the database from this entity's main table to the table and column where
* the entity's finish time is located, or null if this entity
* has no finish time.
* @param propertySpecs a {@link PropertySpec[]} defining the entity's
* properties. These properties should be the same as the corresponding
* propositions' properties. Cannot contain null values.
* @param codeToPropIdMap a one-to-one {@link Map} from code
* to proposition id. If null or a mapping for a code is not
* defined, it is assumed that the code in the database is the same as the
* proposition id.
* @param codeSpec a {@link ColumnSpec} representing the path through the
* database from this entity's main table to the table and column where the
* entity's code is located, or null if this entity has no
* code.
* @param constraintSpecs zero or more {@link ColumnSpec[]} paths from this
* instance's main table to another table and column whose value will
* constrain which rows in the database are members of this entity. Cannot
* contain null values.
* @param valueType if this entity has a value, its {@link ValueType}.
* @param granularity the granularity for interpreting this entity' start
* and finish times.
* @param positionParser a parser for dates/times/timestamps for this
* entity's start and finish times. Cannot be null.
* @param partitionBy a hint to the relational data source backend to
* partition queries for this entity spec by the given {@link Unit}. For
* example, if a time unit of MONTH is specified, the backend may only query
* data one month at a time. In order for this to work, at least one
* {@link org.protempa.dsb.filter.PositionFilter} must be specified that
* defines both upper and lower bounds on the same side of a proposition's
* intervals. If multiple position filters are specified, then one of these
* will be used to partition queries (which one is undefined!). If
* null, no partitioning will occur.
*/
public EntitySpec(String name,
String description,
String[] propositionIds,
boolean unique,
ColumnSpec baseSpec,
ColumnSpec[] uniqueIdSpecs,
ColumnSpec startTimeOrTimestampSpec,
ColumnSpec finishTimeSpec,
PropertySpec[] propertySpecs,
ReferenceSpec[] referenceSpecs,
Map codeToPropIdMap,
ColumnSpec codeSpec,
ColumnSpec[] constraintSpecs,
ColumnSpec valueSpec,
ValueType valueType,
Granularity granularity,
JDBCPositionFormat positionParser,
Unit partitionBy,
int[] maxWidths,
ColumnSpec createDateSpec,
ColumnSpec updateDateSpec,
ColumnSpec deleteDateSpec) {
if (name == null) {
throw new IllegalArgumentException("name cannot be null");
}
this.name = name;
if (positionParser == null
&& (startTimeOrTimestampSpec != null || finishTimeSpec != null)) {
throw new IllegalArgumentException(
"positionParser cannot be null for entities with a start time and/or finish time");
}
if (propositionIds != null) {
this.propositionIds = propositionIds.clone();
ProtempaUtil.internAll(this.propositionIds);
ProtempaUtil.checkArrayForNullElement(this.propositionIds,
"propositionIds");
if (this.propositionIds.length == 0) {
SQLGenUtil.logger().log(Level.WARNING, "No mappings are specified for entity spec {0}", name);
}
if (this.propositionIds.length > 1 && codeSpec == null) {
throw new IllegalArgumentException("if propositionIds has multiple proposition ids, there must be a codeSpec to differentiate between them");
}
} else {
throw new IllegalArgumentException("propositionIds cannot be null");
}
if (baseSpec == null) {
throw new IllegalArgumentException("baseSpec cannot be null");
}
this.baseSpec = baseSpec;
if (description == null) {
description = "";
}
this.description = description;
this.unique = unique;
ProtempaUtil.checkArray(uniqueIdSpecs, "uniqueIdSpecs");
this.uniqueIdSpecs = uniqueIdSpecs;
this.startTimeOrTimestampSpec = startTimeOrTimestampSpec;
this.finishTimeSpec = finishTimeSpec;
if (propertySpecs != null) {
this.propertySpecs = propertySpecs.clone();
ProtempaUtil.checkArrayForNullElement(this.propertySpecs,
"propertySpecs");
} else {
this.propertySpecs = EMPTY_PROPERTY_SPEC_ARRAY;
}
if (referenceSpecs != null) {
ProtempaUtil.checkArrayForNullElement(referenceSpecs,
"referenceSpecs");
checkReferenceSpecArrayForDuplicates(referenceSpecs);
this.referenceSpecs = referenceSpecs.clone();
for (ReferenceSpec rs : this.referenceSpecs) {
rs.setReferringEntitySpec(this);
}
} else {
this.referenceSpecs = EMPTY_REFERENCE_SPEC_ARRAY;
}
if (codeToPropIdMap != null) {
this.codeToPropIdMap = new HashMap<>(codeToPropIdMap);
} else {
this.codeToPropIdMap = Collections.emptyMap();
}
this.codeSpec = codeSpec;
if (constraintSpecs != null) {
this.constraintSpecs = new ColumnSpec[constraintSpecs.length];
System.arraycopy(constraintSpecs, 0, this.constraintSpecs, 0,
constraintSpecs.length);
ProtempaUtil.checkArrayForNullElement(this.constraintSpecs,
"constraintSpecs");
} else {
this.constraintSpecs = EMPTY_COLUMN_SPEC_ARRAY;
}
if (valueType != null && valueSpec == null) {
throw new IllegalArgumentException(
"valueType must have a corresponding valueSpec");
}
if (valueType == null && valueSpec != null) {
throw new IllegalArgumentException(
"valueSpec must have a corresponding valueType");
}
this.valueType = valueType;
this.valueSpec = valueSpec;
this.granularity = granularity;
this.positionParser = positionParser;
this.partitionBy = partitionBy;
if (maxWidths == null) {
this.maxWidths = null;
} else {
if (maxWidths.length != this.uniqueIdSpecs.length) {
throw new IllegalArgumentException(
"maxWidths, if not null, must have the same number of values as uniqueIdSpecs");
}
this.maxWidths = maxWidths.clone();
}
this.createDateSpec = createDateSpec;
this.updateDateSpec = updateDateSpec;
this.deleteDateSpec = deleteDateSpec;
}
/**
* Gets this entity spec's name.
*
* @return a {@link String}.
*/
public String getName() {
return this.name;
}
/**
* Returns a textual description of this entity spec.
*
* @return a {@link String}. Cannot be null.
*/
public String getDescription() {
return this.description;
}
/**
* Returns the proposition ids to which this entity spec applies.
*
* @return a {@link String[]} of proposition ids.
*/
public String[] getPropositionIds() {
return this.propositionIds.clone();
}
/**
* Returns whether each row corresponds to its own instance of this entity.
*
* @return true if rows each correspond to their own instance
* of this entity, false otherwise.
*/
public boolean isUnique() {
return this.unique;
}
/**
* Gets the path through the database from the key's main table to this
* entity's main table.
*
* @return a {@link ColumnSpec} representing this path.
*/
public ColumnSpec getBaseSpec() {
return this.baseSpec;
}
/**
* Gets the paths through the database from this entity's main table to the
* tables and columns that together form an unique identifier for this
* entity.
*
* @return a {@link ColumnSpec[]} representing these paths.
*/
public ColumnSpec[] getUniqueIdSpecs() {
return this.uniqueIdSpecs.clone();
}
/**
* Gets the path through the database from this entity's main table to the
* table and column where the entity's start time (or timestamp, if no
* finish time is defined) is located, or null if this entity
* has no start time or timestamp.
*
* @return a {@link ColumnSpec} representing this path, or null
* if this entity has no start time or timestamp.
*/
public ColumnSpec getStartTimeSpec() {
return this.startTimeOrTimestampSpec;
}
/**
* Gets the path through the database from this entity's main table to the
* table and column where the entity's finish time (if defined) is located.
*
* @return a {@link ColumnSpec} representing this path, or null
* if this entity has no finish time.
*/
public ColumnSpec getFinishTimeSpec() {
return this.finishTimeSpec;
}
/**
* The entity's properties.
*
* @return a {@link PropertySpec[]} of the entity's properties. Guaranteed
* not null.
*/
public PropertySpec[] getPropertySpecs() {
return this.propertySpecs.clone();
}
/**
* The entity's references to other entities.
*
* @return a {@link ReferenceSpec[]} of the entity's references to other
* entities. Guaranteed not null.
*/
public ReferenceSpec[] getReferenceSpecs() {
return this.referenceSpecs.clone();
}
/**
* Returns whether this entity spec has a reference to another entity spec.
*
* @param entitySpec another entity spec.
* @return true or false.
*/
public boolean hasReferenceTo(EntitySpec entitySpec) {
if (entitySpec == null) {
throw new IllegalArgumentException("entitySpec cannot be null");
}
boolean found = false;
String entitySpecName = entitySpec.name;
for (ReferenceSpec refSpec : this.referenceSpecs) {
if (refSpec.getEntityName().equals(entitySpecName)) {
found = true;
continue;
}
}
return found;
}
/**
* Returns the reference specs that point to the given entity spec.
*
* @param entitySpec another entity spec.
* @return a {@link ReferenceSpec[]}.
*/
public ReferenceSpec[] referencesTo(EntitySpec entitySpec) {
if (entitySpec == null) {
throw new IllegalArgumentException("entitySpec cannot be null");
}
List result = new ArrayList<>();
String entitySpecName = entitySpec.name;
for (ReferenceSpec refSpec : this.referenceSpecs) {
if (refSpec.getEntityName().equals(entitySpecName)) {
result.add(refSpec);
}
}
return result.toArray(new ReferenceSpec[result.size()]);
}
/**
* Returns a one-to-one mapping from code to proposition id. If
* null or a mapping for a code is not defined, it is assumed
* that the code in the database is the same as the proposition id.
*
* @return a {@link Map}. Guaranteed not null.
*/
public Map getCodeToPropIdMap() {
Map result
= new HashMap<>(this.codeToPropIdMap);
return result;
}
/**
* Gets the path through the database from this entity's main table to the
* table and column where the a code representing this entity is located.
*
* @return a {@link ColumnSpec}.
*/
public ColumnSpec getCodeSpec() {
return this.codeSpec;
}
/**
* Returns zero or more {@link ColumnSpec[]} paths from this instance's main
* table to another table and column whose value will constrain which rows
* in the database are members of this entity. Cannot contain
* null values.
*
* @return a {@link ColumnSpec[]}.
*/
public ColumnSpec[] getConstraintSpecs() {
return this.constraintSpecs.clone();
}
/**
* Returns this entity's value.
*
* @return a {@link ValueType}, or null if this entity does not
* have a value.
*/
public ValueType getValueType() {
return this.valueType;
}
/**
* Gets the path through the database from this entity's main table to the
* table and column where this entity's value is located.
*
* @return a {@link ColumnSpec}.
*/
public ColumnSpec getValueSpec() {
return this.valueSpec;
}
/**
* Returns the granularity for interpreting this entity' start and finish
* times.
*
* @return a {@link Granularity}.
*/
public Granularity getGranularity() {
return this.granularity;
}
/**
* Returns a parser for dates/times/timestamps for this entity's start and
* finish times in the database.
*
* @return a {@link PositionParser}. Cannot be null.
*/
public JDBCPositionFormat getPositionParser() {
return this.positionParser;
}
/**
* Returns a hint to the relational data source backend to partition queries
* for this entity spec by the given units. For example, if a time unit of
* MONTH is specified, the backend may only query data one month at a time.
* In order for this to work, at least one
* {@link org.protempa.dsb.filter.PositionFilter} must be specified that
* defines both upper and lower bounds on the same side of a proposition's
* intervals. If multiple position filters are specified, then one of these
* will be used to partition queries (which one is undefined!).
*
* @return a {@link Unit} to partition by. If null, no
* partitioning will occur.
*/
public Unit getPartitionBy() {
return this.partitionBy;
}
public int[] getMaxWidths() {
if (this.maxWidths == null) {
return null;
} else {
return maxWidths.clone();
}
}
public ColumnSpec getCreateDateSpec() {
return createDateSpec;
}
public ColumnSpec getUpdateDateSpec() {
return updateDateSpec;
}
public ColumnSpec getDeleteDateSpec() {
return deleteDateSpec;
}
/**
* Returns the distinct tables specified in this entity spec, not including
* references to other entity specs.
*
* @return an array of {@link TableSpec}s. Guaranteed not
* null.
*/
public ColumnSpec[] getColumnSpecs() {
Set results = new HashSet<>();
addTo(results, this.baseSpec);
addTo(results, this.codeSpec);
addTo(results, this.constraintSpecs);
addTo(results, this.finishTimeSpec);
addTo(results, this.startTimeOrTimestampSpec);
addTo(results, this.uniqueIdSpecs);
addTo(results, this.valueSpec);
addTo(results, this.createDateSpec);
addTo(results, this.updateDateSpec);
addTo(results, this.deleteDateSpec);
for (PropertySpec propertySpec : this.propertySpecs) {
addTo(results, propertySpec.getCodeSpec());
}
return results.toArray(new ColumnSpec[results.size()]);
}
private void addTo(Set tableSpecs, ColumnSpec... colSpecs) {
for (ColumnSpec colSpec : colSpecs) {
if (colSpec != null) {
for (ColumnSpec cs : colSpec.asList()) {
tableSpecs.add(cs);
}
}
}
}
@Override
public String toString() {
return new ToStringBuilder(this).append("name", this.name).append("description", this.description).append("propositionIds", this.propositionIds).append("unique", this.unique).append("baseSpec", this.baseSpec).append("uniqueIdSpecs", this.uniqueIdSpecs).append("startTimeOrTimestampSpec", this.startTimeOrTimestampSpec).append("finishTimeSpec", this.finishTimeSpec).append("propertySpecs", this.propertySpecs).append("referenceSpecs", this.referenceSpecs).append("codeToPropIdMap", this.codeToPropIdMap).append("codeSpec", this.codeSpec).append("constraintSpecs", this.constraintSpecs).append("valueType", this.valueType).append("valueSpec", this.valueSpec).append("granularity", this.granularity).append("positionParser", this.positionParser).append("partitionBy", this.partitionBy).toString();
}
private static class RefSpecUID {
String refName;
String entitySpecName;
RefSpecUID(String refName, String entitySpecName) {
this.refName = refName;
this.entitySpecName = entitySpecName;
}
@Override
public int hashCode() {
int result = 17;
int c = this.refName.hashCode();
result = 31 * result + c;
c = this.entitySpecName.hashCode();
result = 31 * result + c;
return result;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final RefSpecUID other = (RefSpecUID) obj;
if (!Objects.equals(this.refName, other.refName)) {
return false;
}
if (!Objects.equals(this.entitySpecName, other.entitySpecName)) {
return false;
}
return true;
}
@Override
public String toString() {
return refName + " to entity " + entitySpecName;
}
}
private static void checkReferenceSpecArrayForDuplicates(
ReferenceSpec[] refSpecs) {
Set duplicates = new HashSet<>();
Set refNames = new HashSet<>();
for (ReferenceSpec refSpec : refSpecs) {
String refName = refSpec.getReferenceName();
String entitySpecName = refSpec.getEntityName();
RefSpecUID uid = new RefSpecUID(refName, entitySpecName);
if (!refNames.add(uid)) {
duplicates.add(uid);
}
}
if (!duplicates.isEmpty()) {
throw new IllegalArgumentException("Duplicate reference names " +
StringUtils.join(duplicates, ", "));
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy