/*******************************************************************************
* 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.xml;
import java.util.Iterator;
import java.util.List;
import org.eclipse.persistence.tools.workbench.mappingsmodel.ProblemConstants;
import org.eclipse.persistence.tools.workbench.mappingsmodel.descriptor.xml.MWEisDescriptor;
import org.eclipse.persistence.tools.workbench.mappingsmodel.mapping.MWCollectionContainerPolicy;
import org.eclipse.persistence.tools.workbench.mappingsmodel.mapping.MWContainerPolicy;
import org.eclipse.persistence.tools.workbench.mappingsmodel.mapping.MWIndirectableContainerMapping;
import org.eclipse.persistence.tools.workbench.mappingsmodel.mapping.MWListContainerPolicy;
import org.eclipse.persistence.tools.workbench.mappingsmodel.mapping.MWMapContainerMapping;
import org.eclipse.persistence.tools.workbench.mappingsmodel.mapping.MWMapContainerPolicy;
import org.eclipse.persistence.tools.workbench.mappingsmodel.mapping.MWMapping;
import org.eclipse.persistence.tools.workbench.mappingsmodel.mapping.MWSetContainerPolicy;
import org.eclipse.persistence.tools.workbench.mappingsmodel.meta.MWClassAttribute;
import org.eclipse.persistence.tools.workbench.mappingsmodel.query.xml.MWEisInteraction;
import org.eclipse.persistence.tools.workbench.mappingsmodel.schema.MWSchemaContextComponent;
import org.eclipse.persistence.tools.workbench.mappingsmodel.xml.MWXmlField;
import org.eclipse.persistence.tools.workbench.mappingsmodel.xml.MWXpathContext;
import org.eclipse.persistence.tools.workbench.mappingsmodel.xml.MWXpathSpec;
import org.eclipse.persistence.tools.workbench.mappingsmodel.xml.SchemaChange;
import org.eclipse.persistence.tools.workbench.utility.filters.Filter;
import org.eclipse.persistence.tools.workbench.utility.iterators.FilteringIterator;
import org.eclipse.persistence.tools.workbench.utility.iterators.NullIterator;
import org.eclipse.persistence.tools.workbench.utility.node.Node;
import org.eclipse.persistence.eis.mappings.EISOneToManyMapping;
import org.eclipse.persistence.internal.indirection.TransparentIndirectionPolicy;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.converters.ObjectTypeConverter;
import org.eclipse.persistence.oxm.XMLDescriptor;
import org.eclipse.persistence.oxm.mappings.XMLCompositeObjectMapping;
import org.eclipse.persistence.oxm.mappings.XMLDirectMapping;
public final class MWEisOneToManyMapping
extends MWEisReferenceMapping
implements MWIndirectableContainerMapping, MWXpathContext, MWMapContainerMapping
{
// **************** Variables *********************************************
private volatile String foreignKeyLocation;
public static final String FOREIGN_KEY_LOCATION_PROPERTY = "foreignKeyLocation";
public static final String KEYS_ON_TARGET = "keysOnTarget";
public static final String KEYS_ON_SOURCE = "keysOnSource";
//if KEYS_ON_SOURCE is the foreignKeyLocation, then the grouping element applies
private MWXmlField foreignKeyGroupingElement;
private volatile MWContainerPolicy containerPolicy;
private volatile MWEisInteraction deleteAllInteraction;
// **************** Constructors ******************************************
/** Default constructor - for TopLink use only */
private MWEisOneToManyMapping() {
super();
}
public MWEisOneToManyMapping(MWEisDescriptor descriptor, MWClassAttribute attribute, String attributeName) {
super(descriptor, attribute, attributeName);
}
// **************** Initialization ****************************************
protected void initialize(Node parent) {
super.initialize(parent);
this.foreignKeyLocation = KEYS_ON_TARGET;
this.foreignKeyGroupingElement = new MWXmlField(this);
this.deleteAllInteraction = new MWEisInteraction(this);
}
/**
* 1:n mappings always have a selection interaction
*/
protected boolean requiresSelectionInteraction() {
return true;
}
protected void initialize(MWClassAttribute attribute, String name) {
super.initialize(attribute, name);
//TODO wow this is not pretty! and it's copied in other collection mappings
if (attribute.isValueHolder()) {
if (attribute.getValueType().isAssignableToMap()) {
this.containerPolicy = new MWMapContainerPolicy(this);
}
else if (attribute.getValueType().isAssignableToList()) {
this.containerPolicy = new MWListContainerPolicy(this);
}
else if (attribute.getValueType().isAssignableToSet()) {
this.containerPolicy = new MWSetContainerPolicy(this);
}
else if (attribute.getValueType().isAssignableToCollection()){
this.containerPolicy = new MWCollectionContainerPolicy(this);
}
else { //This is the default in the runtime
this.containerPolicy = new MWListContainerPolicy(this);
}
setUseValueHolderIndirection();
}
else {
if (attribute.isAssignableToMap()) {
this.containerPolicy = new MWMapContainerPolicy(this);
}
else if (attribute.isAssignableToList()) {
this.containerPolicy = new MWListContainerPolicy(this);
}
else if (attribute.isAssignableToSet()) {
this.containerPolicy = new MWSetContainerPolicy(this);
}
else if (attribute.isAssignableToCollection()){
this.containerPolicy = new MWCollectionContainerPolicy(this);
}
else { //This is the default in the runtime
this.containerPolicy = new MWListContainerPolicy(this);
}
setUseTransparentIndirection();
}
}
protected void addChildrenTo(List children) {
super.addChildrenTo(children);
children.add(this.foreignKeyGroupingElement);
children.add(this.containerPolicy);
children.add(this.deleteAllInteraction);
}
// **************** Foreign Key Location ***************
public boolean foreignKeysAreOnSource() {
return this.foreignKeyLocation == KEYS_ON_SOURCE;
}
public boolean foreignKeysAreOnTarget() {
return this.foreignKeyLocation == KEYS_ON_TARGET;
}
public void setForeignKeysOnSource() {
this.setForeignKeyLocation(KEYS_ON_SOURCE);
}
public void setForeignKeysOnTarget() {
this.setForeignKeyLocation(KEYS_ON_TARGET);
this.getForeignKeyGroupingElement().setXpath("");
this.clearXmlFieldPairs();
}
private void setForeignKeyLocation(String newValue) {
String oldValue = this.foreignKeyLocation;
this.foreignKeyLocation = newValue;
firePropertyChanged(FOREIGN_KEY_LOCATION_PROPERTY, oldValue, newValue);
}
// **************** Grouping element **************************************
public MWXmlField getForeignKeyGroupingElement() {
return this.foreignKeyGroupingElement;
}
// **************** Container policy **************************************
public MWContainerPolicy getContainerPolicy() {
return this.containerPolicy;
}
private void setContainerPolicy(MWContainerPolicy containerPolicy) {
Object oldValue = this.containerPolicy;
this.containerPolicy = containerPolicy;
firePropertyChanged(CONTAINER_POLICY_PROPERTY, oldValue, containerPolicy);
}
public MWMapContainerPolicy setMapContainerPolicy() {
if (this.containerPolicy instanceof MWMapContainerPolicy) {
return (MWMapContainerPolicy) this.containerPolicy;
}
MWMapContainerPolicy cp = new MWMapContainerPolicy(this);
this.setContainerPolicy(cp);
return cp;
}
public MWCollectionContainerPolicy setCollectionContainerPolicy() {
if (this.containerPolicy instanceof MWCollectionContainerPolicy) {
return (MWCollectionContainerPolicy) this.containerPolicy;
}
MWCollectionContainerPolicy cp = new MWCollectionContainerPolicy(this);
this.setContainerPolicy(cp);
return cp;
}
public MWListContainerPolicy setListContainerPolicy() {
if (this.containerPolicy instanceof MWListContainerPolicy) {
return (MWListContainerPolicy) this.containerPolicy;
}
MWListContainerPolicy cp = new MWListContainerPolicy(this);
this.setContainerPolicy(cp);
return cp;
}
public MWSetContainerPolicy setSetContainerPolicy() {
if (this.containerPolicy instanceof MWSetContainerPolicy) {
return (MWSetContainerPolicy) this.containerPolicy;
}
MWSetContainerPolicy cp = new MWSetContainerPolicy(this);
this.setContainerPolicy(cp);
return cp;
}
public Iterator candidateKeyMethods(Filter keyMethodFilter) {
return (this.getReferenceDescriptor() == null) ?
NullIterator.instance()
:
new FilteringIterator(this.getReferenceDescriptor().getMWClass().allInstanceMethods(), keyMethodFilter);
}
// **************** Delete All interaction ********************************
public MWEisInteraction getDeleteAllInteraction() {
return this.deleteAllInteraction;
}
// **************** MWXmlMapping contract *********************************
public MWXmlField firstMappedXmlField() {
return this.getForeignKeyGroupingElement().isResolved() ?
this.getForeignKeyGroupingElement()
:
super.firstMappedXmlField();
}
// **************** MWXpathContext implementation *************************
// This mapping works as the context for the grouping element.
// MWXmlFieldPair acts as the context for the source and target fields.
public MWSchemaContextComponent schemaContext(MWXmlField xmlField) {
return this.eisDescriptor().getSchemaContext();
}
public MWXpathSpec xpathSpec(MWXmlField xmlField) {
return this.buildXpathSpec();
}
protected MWXpathSpec buildXpathSpec() {
return new MWXpathSpec() {
public boolean mayUseCollectionData() {
return true;
}
public boolean mayUseComplexData() {
return true;
}
public boolean mayUseSimpleData() {
return false;
}
};
}
/**
* Return true if a source field may use a collection xpath
* @see MWEisReferenceMapping#sourceFieldMayUseCollectionXpath()
*/
public boolean sourceFieldMayUseCollectionXpath() {
return true;
}
// ************* MWIndirectableCollectionMapping implementation ***********
public boolean usesTransparentIndirection() {
return this.getIndirectionType() == TRANSPARENT_INDIRECTION;
}
public void setUseTransparentIndirection() {
this.setIndirectionType(TRANSPARENT_INDIRECTION);
}
// ************* Problem Handling *****************************************
protected void addProblemsTo(List newProblems) {
super.addProblemsTo(newProblems);
this.checkFieldPairs(newProblems);
this.checkGroupingElement(newProblems);
this.checkGroupingElementForeignKeys(newProblems);
this.checkSelectionInteraction(newProblems);
this.checkDeleteAllInteraction(newProblems);
this.addUsesTransparentIndirectionWhileMaintainsBiDirectionalRelationship(newProblems);
}
private void checkFieldPairs(List newProblems) {
if (this.foreignKeysAreOnSource() && this.xmlFieldPairsSize() == 0) {
newProblems.add(this.buildProblem(ProblemConstants.MAPPING_1_TO_M_FIELD_PAIRS_NOT_SPECIFIED));
}
}
private void checkGroupingElement(List newProblems) {
if (this.xmlFieldPairsSize() > 1 && this.getForeignKeyGroupingElement() == null) {
newProblems.add(this.buildProblem(ProblemConstants.MAPPING_FOREIGN_KEY_GROUPING_ELEMENT_NOT_SPECIFIED));
}
}
private void checkGroupingElementForeignKeys(List newProblems) {
MWXmlField groupingElement = this.getForeignKeyGroupingElement();
if (! groupingElement.isSpecified() || ! groupingElement.isResolved()) {
return;
}
for (Iterator stream = this.xmlFieldPairs(); stream.hasNext(); ) {
MWXmlField foreignKeyField = ((MWXmlFieldPair) stream.next()).getSourceXmlField();
if (foreignKeyField.isSpecified() && ! groupingElement.containsXmlField(foreignKeyField)) {
newProblems.add(this.buildProblem(ProblemConstants.MAPPING_FOREIGN_KEY_NOT_CONTAINED_BY_GROUPING_ELEMENT));
break;
}
}
}
private void checkSelectionInteraction(List newProblems) {
if ( ! this.getSelectionInteraction().isSpecified()) {
newProblems.add(this.buildProblem(ProblemConstants.MAPPING_SELECTION_INTERACTION_NOT_SPECIFIED));
}
}
private void checkDeleteAllInteraction(List newProblems) {
if (this.getDeleteAllInteraction().isSpecified() && ! this.isPrivateOwned()) {
newProblems.add(this.buildProblem(ProblemConstants.MAPPING_DELETE_ALL_INTERACTION_SPECIFIED_BUT_NOT_PRIVATE_OWNED));
}
}
private void addUsesTransparentIndirectionWhileMaintainsBiDirectionalRelationship(List newProblems) {
if (this.maintainsBidirectionalRelationship() && (this.usesNoIndirection() || this.usesValueHolderIndirection())) {
newProblems.add(this.buildProblem(ProblemConstants.MAPPING_COLLECTION_MAINTTAINSBIDIRECTIONAL_NO_TRANSPARENT_INDIRECTION));
}
}
// **************** Morphing **********************************************
protected void initializeOn(MWMapping newMapping) {
newMapping.initializeFromMWEisOneToManyMapping(this);
}
public void initializeFromMWCompositeCollectionMapping(MWCompositeCollectionMapping oldMapping) {
super.initializeFromMWCompositeCollectionMapping(oldMapping);
//getContainerPolicy().initializeFrom(oldMapping.getContainerPolicy());
}
protected void initializeFromMWIndirectableContainerMapping(MWIndirectableContainerMapping oldMapping) {
super.initializeFromMWIndirectableContainerMapping(oldMapping);
if (oldMapping.usesTransparentIndirection()) {
this.setUseTransparentIndirection();
}
}
// **************** Model synchronization *********************************
/** @see MWXmlNode#resolveXpaths */
public void resolveXpaths() {
super.resolveXpaths();
this.foreignKeyGroupingElement.resolveXpaths();
}
/** @see MWXmlNode#schemaChanged(SchemaChange) */
public void schemaChanged(SchemaChange change) {
super.schemaChanged(change);
this.foreignKeyGroupingElement.schemaChanged(change);
}
// **************** Runtime Conversion ************************************
protected DatabaseMapping buildRuntimeMapping() {
return new EISOneToManyMapping();
}
public DatabaseMapping runtimeMapping() {
EISOneToManyMapping mapping = (EISOneToManyMapping) super.runtimeMapping();
if (this.getForeignKeyGroupingElement().isSpecified()) {
mapping.setForeignKeyGroupingElement(this.getForeignKeyGroupingElement().runtimeField().getName());
}
for (Iterator stream = this.xmlFieldPairs(); stream.hasNext(); ) {
((MWXmlFieldPair) stream.next()).addRuntimeForeignKeyField(mapping, this.getForeignKeyGroupingElement());
}
// *** Indirection ***
if (this.usesTransparentIndirection()) {
mapping.setIndirectionPolicy(new TransparentIndirectionPolicy());
}
mapping.setContainerPolicy(getContainerPolicy().runtimeContainerPolicy());
if (this.getDeleteAllInteraction().isSpecified()) {
mapping.setDeleteAllCall(this.getDeleteAllInteraction().runtimeInteraction());
}
return mapping;
}
// **************** TopLink methods ***************************************
public static XMLDescriptor buildDescriptor() {
XMLDescriptor descriptor = new XMLDescriptor();
descriptor.setJavaClass(MWEisOneToManyMapping.class);
descriptor.getInheritancePolicy().setParentClass(MWEisReferenceMapping.class);
// use an object type mapping so we can preserve object identity
ObjectTypeConverter foreignKeyLocationConverter = new ObjectTypeConverter();
foreignKeyLocationConverter.addConversionValue(KEYS_ON_TARGET, KEYS_ON_TARGET);
foreignKeyLocationConverter.addConversionValue(KEYS_ON_SOURCE, KEYS_ON_SOURCE);
XMLDirectMapping foreignKeyLocationMapping = new XMLDirectMapping();
foreignKeyLocationMapping.setAttributeName("foreignKeyLocation");
foreignKeyLocationMapping.setXPath("foreign-key-location/text()");
foreignKeyLocationMapping.setNullValue(KEYS_ON_SOURCE);
foreignKeyLocationMapping.setConverter(foreignKeyLocationConverter);
descriptor.addMapping(foreignKeyLocationMapping);
XMLCompositeObjectMapping groupingElementMapping = new XMLCompositeObjectMapping();
groupingElementMapping.setAttributeName("foreignKeyGroupingElement");
groupingElementMapping.setReferenceClass(MWXmlField.class);
groupingElementMapping.setGetMethodName("getGroupingElementForTopLink");
groupingElementMapping.setSetMethodName("setGroupingElementForTopLink");
groupingElementMapping.setXPath("foreign-key-grouping-element");
descriptor.addMapping(groupingElementMapping);
XMLCompositeObjectMapping containerPolicyMapping = new XMLCompositeObjectMapping();
containerPolicyMapping.setAttributeName("containerPolicy");
containerPolicyMapping.setReferenceClass(MWContainerPolicy.MWContainerPolicyRoot.class);
containerPolicyMapping.setXPath("container-policy");
descriptor.addMapping(containerPolicyMapping);
XMLCompositeObjectMapping deleteAllInteractionMapping = new XMLCompositeObjectMapping();
deleteAllInteractionMapping.setAttributeName("deleteAllInteraction");
deleteAllInteractionMapping.setReferenceClass(MWEisInteraction.class);
deleteAllInteractionMapping.setXPath("delete-all-interaction");
descriptor.addMapping(deleteAllInteractionMapping);
return descriptor;
}
private MWXmlField getGroupingElementForTopLink() {
return ("".equals(this.foreignKeyGroupingElement.getXpath())) ? null : this.foreignKeyGroupingElement;
}
private void setGroupingElementForTopLink(MWXmlField xmlField) {
this.foreignKeyGroupingElement = ((xmlField == null) ? new MWXmlField(this) : xmlField);
}
}