/*******************************************************************************
* Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Oracle - initial API and implementation from Oracle TopLink
******************************************************************************/
package org.eclipse.persistence.tools.workbench.mappingsmodel.mapping.relational;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.Vector;
import org.eclipse.persistence.tools.workbench.mappingsmodel.MWModel;
import org.eclipse.persistence.tools.workbench.mappingsmodel.ProblemConstants;
import org.eclipse.persistence.tools.workbench.mappingsmodel.db.MWColumn;
import org.eclipse.persistence.tools.workbench.mappingsmodel.db.MWColumnPair;
import org.eclipse.persistence.tools.workbench.mappingsmodel.db.MWReference;
import org.eclipse.persistence.tools.workbench.mappingsmodel.descriptor.relational.MWJoinFetchableMapping;
import org.eclipse.persistence.tools.workbench.mappingsmodel.descriptor.relational.MWRelationalClassDescriptor;
import org.eclipse.persistence.tools.workbench.mappingsmodel.descriptor.relational.MWTableDescriptor;
import org.eclipse.persistence.tools.workbench.mappingsmodel.handles.MWColumnPairHandle;
import org.eclipse.persistence.tools.workbench.mappingsmodel.handles.MWHandle;
import org.eclipse.persistence.tools.workbench.mappingsmodel.handles.MWHandle.NodeReferenceScrubber;
import org.eclipse.persistence.tools.workbench.mappingsmodel.mapping.MWDirectMapping;
import org.eclipse.persistence.tools.workbench.mappingsmodel.mapping.MWMapping;
import org.eclipse.persistence.tools.workbench.mappingsmodel.mapping.MWProxyIndirectionMapping;
import org.eclipse.persistence.tools.workbench.mappingsmodel.meta.MWClassAttribute;
import org.eclipse.persistence.tools.workbench.mappingsmodel.meta.MWClassCodeGenPolicy;
import org.eclipse.persistence.tools.workbench.utility.CollectionTools;
import org.eclipse.persistence.tools.workbench.utility.iterators.CloneIterator;
import org.eclipse.persistence.tools.workbench.utility.iterators.TransformationIterator;
import org.eclipse.persistence.tools.workbench.utility.node.Node;
import org.eclipse.persistence.tools.workbench.utility.string.StringTools;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.DescriptorEvent;
import org.eclipse.persistence.internal.indirection.ProxyIndirectionPolicy;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.DirectToFieldMapping;
import org.eclipse.persistence.mappings.OneToOneMapping;
import org.eclipse.persistence.oxm.XMLDescriptor;
import org.eclipse.persistence.oxm.mappings.XMLCompositeCollectionMapping;
import org.eclipse.persistence.oxm.mappings.XMLDirectMapping;
import org.eclipse.persistence.oxm.mappings.XMLTransformationMapping;
import org.eclipse.persistence.sessions.Record;
public final class MWOneToOneMapping
extends MWAbstractTableReferenceMapping
implements MWProxyIndirectionMapping
{
private Collection targetForeignKeyHandles;
public final static String TARGET_FOREIGN_KEYS_COLLECTION = "targetForeignKeys";
private NodeReferenceScrubber targetForeignKeyScrubber;
// ********** constructors **********
/** Default constructor - for TopLink use only */
private MWOneToOneMapping() {
super();
}
MWOneToOneMapping(MWRelationalClassDescriptor descriptor, MWClassAttribute attribute, String mappingName) {
super(descriptor, attribute, mappingName);
}
// **************** Initialization ***************
/**
* initialize persistent state
*/
protected void initialize(Node parent) {
super.initialize(parent);
this.targetForeignKeyHandles = new Vector();
}
protected void initialize(MWClassAttribute attribute, String name) {
super.initialize(attribute, name);
if (!getInstanceVariable().isValueHolder() && getInstanceVariable().getType().isInterface()) {
this.indirectionType = PROXY_INDIRECTION;
}
if (getProject().usesWeaving()) {
this.indirectionType = VALUE_HOLDER_INDIRECTION;
}
}
// **************** Containment Hierarchy ***************
protected void addChildrenTo(List children) {
super.addChildrenTo(children);
synchronized (this.targetForeignKeyHandles) { children.addAll(this.targetForeignKeyHandles); }
}
private NodeReferenceScrubber targetForeignKeyScrubber() {
if (this.targetForeignKeyScrubber == null) {
this.targetForeignKeyScrubber = this.buildTargetForeignKeyScrubber();
}
return this.targetForeignKeyScrubber;
}
private NodeReferenceScrubber buildTargetForeignKeyScrubber() {
return new NodeReferenceScrubber() {
public void nodeReferenceRemoved(Node node, MWHandle handle) {
MWOneToOneMapping.this.removeTargetForeignKeyHandle((MWColumnPairHandle) handle);
}
public String toString() {
return "MWOneToOneMapping.buildTargetForeignKeyScrubber()";
}
};
}
@Override
protected void automapIndirection() {
if (this.getInstanceVariable().isValueHolder() || getProject().usesWeaving()) {
this.setUseValueHolderIndirection();
}
}
// **************** Accessors ********************
private Iterator targetForeignKeyHandles() {
return new CloneIterator(this.targetForeignKeyHandles) {
protected void remove(Object current) {
MWOneToOneMapping.this.removeTargetForeignKeyHandle((MWColumnPairHandle) current);
}
};
}
void removeTargetForeignKeyHandle(MWColumnPairHandle handle) {
this.targetForeignKeyHandles.remove(handle);
this.fireItemRemoved(TARGET_FOREIGN_KEYS_COLLECTION, handle.getColumnPair());
}
public Iterator targetForeignKeys() {
return new TransformationIterator(this.targetForeignKeyHandles()) {
protected Object transform(Object next) {
return ((MWColumnPairHandle) next).getColumnPair();
}
};
}
public int targetForeignKeysSize() {
return this.targetForeignKeyHandles.size();
}
public void addTargetForeignKey(MWColumnPair targetForeignKey) {
this.targetForeignKeyHandles.add(new MWColumnPairHandle(this, targetForeignKey, this.targetForeignKeyScrubber()));
this.fireItemAdded(TARGET_FOREIGN_KEYS_COLLECTION, targetForeignKey);
}
public void removeTargetForeignKey(MWColumnPair targetForeignKey) {
for (Iterator stream = this.targetForeignKeys(); stream.hasNext(); ) {
if (stream.next() == targetForeignKey) {
stream.remove();
return;
}
}
throw new IllegalArgumentException(targetForeignKey.toString());
}
public void clearTargetForeignKeys() {
for (Iterator stream = this.targetForeignKeyHandles(); stream.hasNext(); ) {
stream.next();
stream.remove();
}
}
public void setReference(MWReference reference) {
if (this.getReference() != reference) {
this.clearTargetForeignKeys();
}
super.setReference(reference);
}
// *********** MWProxyIndirectionMapping implementation ***********
public boolean usesProxyIndirection() {
return getIndirectionType() == PROXY_INDIRECTION;
}
public void setUseProxyIndirection() {
setIndirectionType(PROXY_INDIRECTION);
}
// **************** Queries ******************
public boolean containsTargetForeignKey(MWColumnPair targetForeignKey) {
return CollectionTools.contains(this.targetForeignKeys(), targetForeignKey);
}
protected boolean fieldIsWritten(MWColumnPair columnPair) {
return ! this.containsTargetForeignKey(columnPair);
}
// **************** Automap Support **********
/**
* one-to-one mappings can be either "source foreign key"
* or "target foreign key"
*/
protected Set buildCandidateReferences() {
Set references = new HashSet();
references.addAll(this.buildCandidateSourceReferences());
references.addAll(this.buildCandidateTargetReferences());
return references;
}
/**
* Returns true if this mapping behaves like a 1-1, where the source table (e.g. EMPLOYEE) has
* a foreign key to the target table (e.g., ADDRESS);
**/
public boolean sourceReferenceFieldsAreFromSourceDescriptorTables() {
return true;
}
public boolean isOneToOneMapping(){
return true;
}
// **************** Mapping Morphing support ******************
public MWOneToOneMapping asMWOneToOneMapping() {
return this;
}
/**
* IMPORTANT: See MWMapping class comment.
*/
protected void initializeOn(MWMapping newMapping) {
newMapping.initializeFromMWOneToOneMapping(this);
}
// protected void initializeFromMWAggregateMapping(MWAggregateMapping oldMapping) {
// super.initializeFromMWAggregateMapping(oldMapping);
// // set up default indirection policy
// if (this.getInstanceVariable() != null) {
// this.getIndirectionPolicy().setUsesIndirection(this.getInstanceVariable().isValueHolder());
// }
// }
//
// protected void initializeFromMWCollectionMapping(MWCollectionMapping oldMapping) {
// super.initializeFromMWCollectionMapping(oldMapping);
// getIndirectionPolicy().setUsesIndirection(!oldMapping.usesTransparentIndirection());
// }
//
// protected void initializeFromMWDirectCollectionMapping(MWDirectCollectionMapping oldMapping) {
// super.initializeFromMWDirectCollectionMapping(oldMapping);
// getIndirectionPolicy().setUsesIndirection(!oldMapping.usesTransparentIndirection());
// }
public void initializeFromMWDirectMapping(MWDirectMapping oldMapping) {
super.initializeFromMWDirectMapping(oldMapping);
// set up default indirection policy
//TODO this doesn't seem necessary, it happens in the initialization
// if (this.getInstanceVariable() != null) {
// this.getIndirectionPolicy().setUsesIndirection(this.getInstanceVariable().isValueHolder());
// }
}
// public void initializeFromMWTransformationMapping(MWTransformationMapping oldMapping) {
// super.initializeFromMWTransformationMapping(oldMapping);
// this.setUsesIndirection(oldMapping.usesIndirection());
// }
public void initializeFromMWVariableOneToOneMapping(MWVariableOneToOneMapping oldMapping) {
super.initializeFromMWVariableOneToOneMapping(oldMapping);
if (oldMapping.usesValueHolderIndirection()) {
setUseValueHolderIndirection();
}
else if (oldMapping.usesNoIndirection()){
setUseNoIndirection();
}
}
// **************** Code generation support ******************
/**
* Used for code gen.
* See MWRMapping.initialValue()
*/
public String initialValue(MWClassCodeGenPolicy classCodeGenPolicy) {
String initialValue = super.initialValue(classCodeGenPolicy);
if (this.isResponsibleForWritingPrimaryKey()) {
initialValue = StringTools.CR
+ "\t\t"
+ classCodeGenPolicy.oneToOneMappingThatControlsWritingOfPrimaryKeyComment(this)
+ StringTools.CR
+ "\t\t"
+ this.getInstanceVariable().initialValueSourceCodeFor(null);
}
return initialValue;
}
protected boolean isResponsibleForWritingPrimaryKey() {
if (parentDescriptorIsAggregate() || this.isReadOnly())
return false;
Collection writableFields = new ArrayList();
addWrittenFieldsTo(writableFields);
for (Iterator stream = ((MWTableDescriptor) getParentDescriptor()).primaryKeyPolicy().primaryKeys(); stream.hasNext(); ) {
if (writableFields.contains(stream.next())) {
return true;
}
}
return false;
}
// **************** Aggregate Support **************
protected Collection buildAggregateFieldNameGenerators() {
Collection generators = super.buildAggregateFieldNameGenerators();
if (getReference() != null) {
for (Iterator stream = getReference().columnPairs(); stream.hasNext(); ) {
generators.add(new ColumnPairAggregateRuntimeFieldNameGenerator(this, (MWColumnPair) stream.next(), true));
}
}
return generators;
}
// *************** MWQueryable implementation ************************
public boolean usesAnyOf() {
return false;
}
public boolean isTraversableForReadAllQueryOrderable() {
return true;
}
public String iconKey() {
return "mapping.oneToOne";
}
// *************** Problem Handling ************************
protected void addProblemsTo(List newProblems) {
super.addProblemsTo(newProblems);
this.addUsesIndirectionWhileMaintainsBiDirectionalRelationship(newProblems);
}
private void addUsesIndirectionWhileMaintainsBiDirectionalRelationship(List newProblems) {
if (this.maintainsBidirectionalRelationship() && this.usesNoIndirection()) {
newProblems.add(this.buildProblem(ProblemConstants.MAPPING_REFERENCE_MAINTAINS_BIDI_BUT_NO_INDIRECTION));
}
}
public void addWrittenFieldsTo(Collection writtenFields) {
if (isReadOnly()) {
return;
}
if (getReference() != null) {
for (Iterator stream = getReference().columnPairs(); stream.hasNext(); ) {
MWColumnPair pair = (MWColumnPair) stream.next();
if ( ! containsTargetForeignKey(pair) && pair.getSourceColumn() != null) {
writtenFields.add(pair.getSourceColumn());
}
}
}
}
// **************** Runtime Conversion *****************
protected DatabaseMapping buildRuntimeMapping() {
return new OneToOneMapping();
}
public DatabaseMapping runtimeMapping() {
OneToOneMapping runtimeMapping = (OneToOneMapping) super.runtimeMapping();
if (this.getReference() == null)
return runtimeMapping;
for (Iterator stream = getReference().columnPairs(); stream.hasNext(); ) {
MWColumnPair pair = (MWColumnPair) stream.next();
MWColumn foreignKeyColumn = pair.getSourceColumn();
MWColumn primaryKeyColumn = pair.getTargetColumn();
if ((foreignKeyColumn != null) && (primaryKeyColumn != null)) {
if (this.containsTargetForeignKey(pair)) {
// Bug fix: If the user has set up a reference from an owning-descriptor associated table
// to a target-descriptor associated table, but also marked it as target foreign key
// (in essence reversing the reference by context), we should reverse the two fields.
if (CollectionTools.contains(getParentRelationalDescriptor().candidateTables(), pair.sourceTable())
&& ! CollectionTools.contains(getParentRelationalDescriptor().candidateTables(), pair.targetTable()))
{
foreignKeyColumn = pair.getTargetColumn();
primaryKeyColumn = pair.getSourceColumn();
}
// End bug fix
if ( ! parentDescriptorIsAggregate()) {
runtimeMapping.addTargetForeignKeyFieldName(foreignKeyColumn.qualifiedName(), primaryKeyColumn.qualifiedName());
} else {
runtimeMapping.addTargetForeignKeyFieldName(foreignKeyColumn.qualifiedName(), getName() + "->" + primaryKeyColumn.qualifiedName() + "_IN_REFERENCE_" + getReference().getName());
}
} else {
if ( ! parentDescriptorIsAggregate()) {
runtimeMapping.addForeignKeyFieldName(foreignKeyColumn.qualifiedName(), primaryKeyColumn.qualifiedName());
} else {
runtimeMapping.addForeignKeyFieldName(getName() + "->" + foreignKeyColumn.getName() + "_IN_REFERENCE_" + getReference().getName(), primaryKeyColumn.qualifiedName());
}
}
}
}
if (usesProxyIndirection()) {
runtimeMapping.setIndirectionPolicy(new ProxyIndirectionPolicy());
}
return runtimeMapping;
}
// *************** TopLink methods *************
public static XMLDescriptor buildDescriptor() {
XMLDescriptor descriptor = new XMLDescriptor();
descriptor.setJavaClass(MWOneToOneMapping.class);
descriptor.getInheritancePolicy().setParentClass(MWAbstractTableReferenceMapping.class);
XMLCompositeCollectionMapping targetForeignKeyHandlesMapping = new XMLCompositeCollectionMapping();
targetForeignKeyHandlesMapping.setAttributeName("targetForeignKeyHandles");
targetForeignKeyHandlesMapping.setGetMethodName("getTargetForeignKeyHandlesForTopLink");
targetForeignKeyHandlesMapping.setSetMethodName("setTargetForeignKeyHandlesForTopLink");
targetForeignKeyHandlesMapping.setReferenceClass(MWColumnPairHandle.class);
targetForeignKeyHandlesMapping.setXPath("target-foreign-key-handles/column-pair-handle");
descriptor.addMapping(targetForeignKeyHandlesMapping);
return descriptor;
}
private Collection getTargetForeignKeyHandlesForTopLink() {
synchronized (this.targetForeignKeyHandles) {
return new TreeSet(this.targetForeignKeyHandles);
}
}
private void setTargetForeignKeyHandlesForTopLink(Collection handles) {
for (Iterator stream = handles.iterator(); stream.hasNext(); ) {
((MWColumnPairHandle) stream.next()).setScrubber(this.targetForeignKeyScrubber());
}
this.targetForeignKeyHandles = handles;
}
public static XMLDescriptor legacy60BuildDescriptor() {
XMLDescriptor descriptor = MWModel.legacy60BuildStandardDescriptor();
descriptor.setJavaClass(MWOneToOneMapping.class);
descriptor.getInheritancePolicy().setParentClass(MWAbstractTableReferenceMapping.class);
((XMLDirectMapping)descriptor.addDirectMapping("usesJoining", "legacyGetUsesJoiningForToplink", "legacySetUsesJoiningForToplink", "uses-joining")).setNullValue(Boolean.FALSE);
XMLCompositeCollectionMapping targetForeignKeyHandlesMapping = new XMLCompositeCollectionMapping();
targetForeignKeyHandlesMapping.setAttributeName("targetForeignKeyHandles");
targetForeignKeyHandlesMapping.setGetMethodName("getTargetForeignKeyHandlesForTopLink");
targetForeignKeyHandlesMapping.setSetMethodName("setTargetForeignKeyHandlesForTopLink");
targetForeignKeyHandlesMapping.setReferenceClass(MWColumnPairHandle.class);
targetForeignKeyHandlesMapping.setXPath("target-foreign-key-handles/column-pair-handle");
descriptor.addMapping(targetForeignKeyHandlesMapping);
return descriptor;
}
private boolean legacyGetUsesJoiningForToplink() {
//should not be called
throw new UnsupportedOperationException();
}
private void legacySetUsesJoiningForToplink(boolean usesJoining) {
if (usesJoining) {
this.setJoinFetchingForToplink(MWJoinFetchableMapping.JOIN_FETCH_INNER);
} else {
this.setJoinFetchingForToplink(MWJoinFetchableMapping.JOIN_FETCH_NONE);
}
}
}