/*
* Copyright (c) 2015 Data Harmonisation Panel
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution. If not, see <http://www.gnu.org/licenses/>.
*
* Contributors:
* Data Harmonisation Panel <http://www.dhpanel.eu>
*/
package eu.esdihumboldt.hale.io.appschema.writer.internal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import com.google.common.base.Joiner;
import eu.esdihumboldt.hale.common.align.model.ChildContext;
import eu.esdihumboldt.hale.common.align.model.impl.PropertyEntityDefinition;
import eu.esdihumboldt.hale.common.schema.model.ChildDefinition;
import eu.esdihumboldt.hale.common.schema.model.TypeDefinition;
import eu.esdihumboldt.hale.io.appschema.AppSchemaIO;
import eu.esdihumboldt.hale.io.appschema.impl.internal.generated.app_schema.AppSchemaDataAccessType;
import eu.esdihumboldt.hale.io.appschema.impl.internal.generated.app_schema.AttributeExpressionMappingType;
import eu.esdihumboldt.hale.io.appschema.impl.internal.generated.app_schema.AttributeExpressionMappingType.Expression;
import eu.esdihumboldt.hale.io.appschema.impl.internal.generated.app_schema.AttributeMappingType;
import eu.esdihumboldt.hale.io.appschema.impl.internal.generated.app_schema.AttributeMappingType.ClientProperty;
import eu.esdihumboldt.hale.io.appschema.impl.internal.generated.app_schema.IncludesPropertyType;
import eu.esdihumboldt.hale.io.appschema.impl.internal.generated.app_schema.NamespacesPropertyType;
import eu.esdihumboldt.hale.io.appschema.impl.internal.generated.app_schema.NamespacesPropertyType.Namespace;
import eu.esdihumboldt.hale.io.appschema.impl.internal.generated.app_schema.SourceDataStoresPropertyType;
import eu.esdihumboldt.hale.io.appschema.impl.internal.generated.app_schema.SourceDataStoresPropertyType.DataStore;
import eu.esdihumboldt.hale.io.appschema.impl.internal.generated.app_schema.SourceDataStoresPropertyType.DataStore.Parameters;
import eu.esdihumboldt.hale.io.appschema.impl.internal.generated.app_schema.SourceDataStoresPropertyType.DataStore.Parameters.Parameter;
import eu.esdihumboldt.hale.io.appschema.impl.internal.generated.app_schema.TargetTypesPropertyType;
import eu.esdihumboldt.hale.io.appschema.impl.internal.generated.app_schema.TargetTypesPropertyType.FeatureType;
import eu.esdihumboldt.hale.io.appschema.impl.internal.generated.app_schema.TypeMappingsPropertyType;
import eu.esdihumboldt.hale.io.appschema.impl.internal.generated.app_schema.TypeMappingsPropertyType.FeatureTypeMapping;
import eu.esdihumboldt.hale.io.appschema.impl.internal.generated.app_schema.TypeMappingsPropertyType.FeatureTypeMapping.AttributeMappings;
import eu.esdihumboldt.hale.io.appschema.writer.AppSchemaMappingUtils;
/**
* App-schema mapping configuration wrapper.
*
* <p>
* Holds the state associated to the same mapping configuration and provides
* utility methods to mutate it.
* </p>
*
* @author Stefano Costa, GeoSolutions
*/
public class AppSchemaMappingWrapper {
/**
* Base name for special attributes used for feature chaining.
*/
public static final String FEATURE_LINK_FIELD = "FEATURE_LINK";
private final String defaultPrefix = "nns";
private int prefixCounter = 1;
private final Map<String, Namespace> namespaceUriMap;
private final Map<String, Namespace> namespacePrefixMap;
private final Map<Integer, FeatureTypeMapping> featureTypeMappings;
private final Map<Integer, Integer> featureLinkCounter;
private final Map<Integer, AttributeMappingType> attributeMappings;
private final Map<String, Set<FeatureTypeMapping>> featureTypesByTargetElement;
private final Map<String, Set<FeatureTypeMapping>> nonFeatureTypesByTargetElement;
private final AppSchemaDataAccessType appSchemaMapping;
/**
* Constructor.
*
* @param appSchemaMapping the app-schema mapping to wrap
*/
public AppSchemaMappingWrapper(AppSchemaDataAccessType appSchemaMapping) {
this.appSchemaMapping = appSchemaMapping;
initMapping(this.appSchemaMapping);
this.namespaceUriMap = new HashMap<String, Namespace>();
this.namespacePrefixMap = new HashMap<String, Namespace>();
this.featureTypeMappings = new HashMap<Integer, FeatureTypeMapping>();
this.featureLinkCounter = new HashMap<Integer, Integer>();
this.attributeMappings = new HashMap<Integer, AttributeMappingType>();
this.featureTypesByTargetElement = new HashMap<String, Set<FeatureTypeMapping>>();
this.nonFeatureTypesByTargetElement = new HashMap<String, Set<FeatureTypeMapping>>();
}
/**
* Return the configuration of the default datastore.
*
* <p>
* An empty datastore configuration is created if none is available.
* </p>
*
* @return the default datastore's configuration.
*/
public DataStore getDefaultDataStore() {
List<DataStore> dataStores = appSchemaMapping.getSourceDataStores().getDataStore();
if (dataStores.size() == 0) {
DataStore defaultDS = new DataStore();
defaultDS.setId(UUID.randomUUID().toString());
defaultDS.setParameters(new Parameters());
dataStores.add(defaultDS);
}
return dataStores.get(0);
}
/**
* Return a namespace object with the provided URI and prefix.
*
* <p>
* If a namespace object for the same URI already exists, it is returned.
* Otherwise, a new one is created.
* </p>
* <p>
* If the prefix is empty, a non-empty prefix is automatically generated.
* If, in a subsequent call to this method, a non-empty prefix is provided,
* the user-provided prefix will replace the generated one.
* </p>
*
* @param namespaceURI the namespace URI
* @param prefix the namespace prefix
* @return the created namespace object
*/
public Namespace getOrCreateNamespace(String namespaceURI, String prefix) {
if (namespaceURI != null && !namespaceURI.isEmpty()) {
if (!namespaceUriMap.containsKey(namespaceURI)) {
if (prefix == null || prefix.trim().isEmpty()) {
prefix = defaultPrefix;
}
// make sure prefix is unique
String uniquePrefix = prefix;
while (namespacePrefixMap.containsKey(uniquePrefix)) {
uniquePrefix = prefix + prefixCounter;
prefixCounter++;
}
Namespace ns = new Namespace();
ns.setPrefix(uniquePrefix);
ns.setUri(namespaceURI);
namespaceUriMap.put(namespaceURI, ns);
namespacePrefixMap.put(uniquePrefix, ns);
appSchemaMapping.getNamespaces().getNamespace().add(ns);
return ns;
}
else {
// update prefix if provided prefix is not empty and currently
// assigned prefix was made up
Namespace ns = namespaceUriMap.get(namespaceURI);
if (prefix != null && !prefix.isEmpty() && ns.getPrefix().startsWith(defaultPrefix)) {
// // check prefix is unique
// if (!namespacePrefixMap.containsKey(prefix)) {
// remove old prefix-NS mapping from namespacePrefixMap
namespacePrefixMap.remove(ns.getPrefix());
// add new prefix-NS mapping to namespacePrefixMap
ns.setPrefix(prefix);
namespacePrefixMap.put(prefix, ns);
// }
}
return ns;
}
}
else {
return null;
}
}
/**
* Add a schema URI to the list of target types.
*
* @param schemaURI the schema URI
*/
public void addSchemaURI(String schemaURI) {
if (schemaURI != null && !schemaURI.isEmpty()) {
this.appSchemaMapping.getTargetTypes().getFeatureType().getSchemaUri().add(schemaURI);
}
}
/**
* Updates a schema URI in the generated mapping configuration.
*
* @param oldSchemaURI the current schema URI
* @param newSchemaURI the updated schema URI
*/
public void updateSchemaURI(String oldSchemaURI, String newSchemaURI) {
if (oldSchemaURI != null && !oldSchemaURI.isEmpty() && newSchemaURI != null
&& !newSchemaURI.isEmpty()) {
List<String> uris = this.appSchemaMapping.getTargetTypes().getFeatureType()
.getSchemaUri();
if (uris.contains(oldSchemaURI)) {
uris.remove(oldSchemaURI);
uris.add(newSchemaURI);
}
}
}
/**
* @see AppSchemaMappingWrapper#buildAttributeXPath(TypeDefinition, List)
*
* @param owningType the type owning the target property
* @param propertyEntityDef the target property definition
* @return the XPath expression pointing to the target property
*/
public String buildAttributeXPath(TypeDefinition owningType,
PropertyEntityDefinition propertyEntityDef) {
List<ChildContext> propertyPath = propertyEntityDef.getPropertyPath();
return buildAttributeXPath(owningType, propertyPath);
}
/**
* Build an XPath expression to be used as <targetAttribute> for the
* provided target property definition.
*
* <p>
* The algorithm to build the path is as follows:
* <ol>
* <li>the property path is traversed backwards, from end to beginning</li>
* <li>on each step, a new path segment is added at the top of the list, but
* only if the child definition describes a property and not a group</li>
* <li>on each step, if a non-null context name is defined on the child
* context, <code>[<context name>]</code> string is appended to the
* path segment</li>
* <li>the traversal stops when the parent type of the last visited property
* equals to the provided owning type</li>
* </ol>
*
* @param owningType the type owning the target property
* @param propertyPath the target property path
* @return the XPath expression pointing to the target property
*/
public String buildAttributeXPath(TypeDefinition owningType, List<ChildContext> propertyPath) {
List<String> pathSegments = new ArrayList<String>();
for (int i = propertyPath.size() - 1; i >= 0; i--) {
ChildContext childContext = propertyPath.get(i);
// TODO: how to handle conditions?
Integer contextId = childContext.getContextName();
ChildDefinition<?> child = childContext.getChild();
// only properties (not groups) are taken into account in building
// the xpath expression
if (child.asProperty() != null) {
String namespaceURI = child.getName().getNamespaceURI();
String prefix = child.getName().getPrefix();
String name = child.getName().getLocalPart();
Namespace ns = getOrCreateNamespace(namespaceURI, prefix);
String path = ns.getPrefix() + ":" + name;
if (contextId != null) {
// XPath indices start from 1, whereas contextId starts from
// 0 --> add 1
path = String.format("%s[%d]", path, contextId + 1);
}
// insert path segment at the first position
pathSegments.add(0, path);
}
if (child.getParentType() != null
&& child.getParentType().getName().equals(owningType.getName())) {
// I reached the owning type: stop walking the path
break;
}
}
String xPath = Joiner.on("/").join(pathSegments);
return xPath;
}
/**
* Return the feature type mapping associated to the provided type.
*
* <p>
* If a feature type mapping for the provided type already exists, it is
* returned; otherwise, a new one is created.
* </p>
*
* @param targetType the target type
* @return the feature type mapping
*/
public FeatureTypeMapping getOrCreateFeatureTypeMapping(TypeDefinition targetType) {
return getOrCreateFeatureTypeMapping(targetType, null);
}
/**
* Return the feature type mapping associated to the provided type and
* mapping name.
*
* <p>
* If a feature type mapping for the provided type and mapping name already
* exists, it is returned; otherwise, a new one is created.
* </p>
*
* @param targetType the target type
* @param mappingName the mapping name
* @return the feature type mapping
*/
public FeatureTypeMapping getOrCreateFeatureTypeMapping(TypeDefinition targetType,
String mappingName) {
if (targetType == null) {
return null;
}
Integer hashKey = getFeatureTypeMappingHashKey(targetType, mappingName);
if (!featureTypeMappings.containsKey(hashKey)) {
// create
FeatureTypeMapping featureTypeMapping = new FeatureTypeMapping();
// initialize attribute mappings member
featureTypeMapping.setAttributeMappings(new AttributeMappings());
// TODO: how do I know the datasource from which data will be read?
featureTypeMapping.setSourceDataStore(getDefaultDataStore().getId());
// TODO: I'm getting the element name with
// targetType.getDisplayName():
// isn't there a more elegant (and perhaps more reliable) way to
// know which element corresponds to a type?
featureTypeMapping.setTargetElement(targetType.getName().getPrefix() + ":"
+ targetType.getDisplayName());
if (mappingName != null && !mappingName.isEmpty()) {
featureTypeMapping.setMappingName(mappingName);
}
appSchemaMapping.getTypeMappings().getFeatureTypeMapping().add(featureTypeMapping);
featureTypeMappings.put(hashKey, featureTypeMapping);
addToFeatureTypeMappings(targetType, featureTypeMapping);
}
return featureTypeMappings.get(hashKey);
}
private Integer getFeatureTypeMappingHashKey(TypeDefinition targetType, String mappingName) {
String hashBase = targetType.getName().toString();
if (mappingName != null && !mappingName.isEmpty()) {
hashBase += "__" + mappingName;
}
return hashBase.hashCode();
}
private void addToFeatureTypeMappings(TypeDefinition targetType, FeatureTypeMapping typeMapping) {
Map<String, Set<FeatureTypeMapping>> mappingsByTargetElement = null;
if (AppSchemaMappingUtils.isFeatureType(targetType)) {
mappingsByTargetElement = featureTypesByTargetElement;
}
else {
mappingsByTargetElement = nonFeatureTypesByTargetElement;
}
if (!mappingsByTargetElement.containsKey(typeMapping.getTargetElement())) {
mappingsByTargetElement.put(typeMapping.getTargetElement(),
new HashSet<FeatureTypeMapping>());
}
mappingsByTargetElement.get(typeMapping.getTargetElement()).add(typeMapping);
}
/**
* Returns the value of the <code><targetElement></code> tag for all
* feature types in the mapping configuration.
*
* @return the set of feature type element names
*/
public Set<String> getFeatureTypeElements() {
return featureTypesByTargetElement.keySet();
}
/**
* Returns the value of the <code><targetElement></code> tag for all
* non-feature types in the mapping configuration.
*
* @return the set of non-feature type element names
*/
public Set<String> getNonFeatureTypeElements() {
return nonFeatureTypesByTargetElement.keySet();
}
/**
* Returns all configured mappings for the provided feature type.
*
* @param featureTypeElement the feature type's element name
* @return the mappings
*/
public Set<FeatureTypeMapping> getFeatureTypeMappings(String featureTypeElement) {
return getTypeMappingsByElement(featureTypesByTargetElement, featureTypeElement);
}
/**
* Returns all configured mappings for the provided non-feature type.
*
* @param nonFeatureTypeElement the non-feature type's element name
* @return the mappings
*/
public Set<FeatureTypeMapping> getNonFeatureTypeMappings(String nonFeatureTypeElement) {
return getTypeMappingsByElement(nonFeatureTypesByTargetElement, nonFeatureTypeElement);
}
private Set<FeatureTypeMapping> getTypeMappingsByElement(
Map<String, Set<FeatureTypeMapping>> mappingsByTargetElement, String typeElement) {
Set<FeatureTypeMapping> mappings = null;
if (mappingsByTargetElement.containsKey(typeElement)) {
mappings = mappingsByTargetElement.get(typeElement);
}
else {
mappings = Collections.emptySet();
}
return mappings;
}
/**
* Returns the unique <code>FEATURE_LINK</code> attribute name for the
* specified feature type mapping.
*
* <p>
* E.g. the first time the method is called, it will return
* <code>FEATURE_LINK[1]</code>; if it is called a second time, with the
* same input parameters, it will return <code>FEATURE_LINK[2]</code>, and
* so on.
* </p>
*
* @param featureType the feature type
* @param mappingName the feature type's mapping name (may be
* <code>null</code>)
* @return a unique <code>FEATURE_LINK[i]</code> attribute name
*/
public String getUniqueFeatureLinkAttribute(TypeDefinition featureType, String mappingName) {
Integer featureTypeKey = getFeatureTypeMappingHashKey(featureType, mappingName);
if (!featureLinkCounter.containsKey(featureTypeKey)) {
featureLinkCounter.put(featureTypeKey, 0);
}
Integer counter = featureLinkCounter.get(featureTypeKey);
// update counter
featureLinkCounter.put(featureTypeKey, ++counter);
return String.format("%s[%d]", FEATURE_LINK_FIELD, counter);
}
/**
* Return the attribute mapping associated to the provided property.
*
* <p>
* If an attribute mapping for the provided property already exists, it is
* returned; otherwise, a new one is created.
* </p>
*
* @param owningType the type owning the property
* @param mappingName the mapping name
* @param propertyPath the property path
* @return the attribute mapping
*/
public AttributeMappingType getOrCreateAttributeMapping(TypeDefinition owningType,
String mappingName, List<ChildContext> propertyPath) {
if (propertyPath == null || propertyPath.isEmpty()) {
return null;
}
Integer hashKey = getAttruteMappingHashKey(owningType, propertyPath);
if (!attributeMappings.containsKey(hashKey)) {
// create
AttributeMappingType attrMapping = new AttributeMappingType();
// add to owning type mapping
FeatureTypeMapping ftMapping = getOrCreateFeatureTypeMapping(owningType, mappingName);
ftMapping.getAttributeMappings().getAttributeMapping().add(attrMapping);
// put into internal map
attributeMappings.put(hashKey, attrMapping);
}
return attributeMappings.get(hashKey);
}
private Integer getAttruteMappingHashKey(TypeDefinition owningType,
List<ChildContext> propertyPath) {
final String SEPARATOR = "__";
StringBuilder pathBuilder = new StringBuilder();
if (owningType != null) {
pathBuilder.append(owningType.getName().toString()).append(SEPARATOR);
for (ChildContext childContext : propertyPath) {
pathBuilder.append(childContext.getChild().getName().toString());
if (childContext.getContextName() != null) {
pathBuilder.append(childContext.getContextName());
}
pathBuilder.append(SEPARATOR);
}
}
else {
throw new IllegalArgumentException("Could not find feature type owning property");
}
return pathBuilder.toString().hashCode();
}
/**
* @return a copy of the wrapped app-schema mapping
*/
public AppSchemaDataAccessType getAppSchemaMapping() {
return cloneMapping(appSchemaMapping);
}
/**
* Returns true if the wrapped app-schema mapping configuration must be
* split in multiple files.
*
* <p>
* The configuration will be split in a main file containing mappings for
* all top-level feature types, and a second file containing mappings for
* non-feature types (and alternative mappings for the feature types
* configured in the main file).
* </p>
*
* @return true if multiple files are required to store the mapping
* configuration, false otherwise
*/
public boolean requiresMultipleFiles() {
// if non-feature type mappings are present, return true
if (nonFeatureTypesByTargetElement.size() > 0) {
return true;
}
// check whether multiple mappings of the same feature type are present
for (String targetElement : featureTypesByTargetElement.keySet()) {
if (featureTypesByTargetElement.get(targetElement).size() > 1) {
return true;
}
}
// don't need multiple files
return false;
}
/**
* Returns the mapping configuration for the main mapping file.
*
* <p>
* If the mapping does not require multiple files, this method is equivalent
* to {@link #getAppSchemaMapping()}.
* </p>
*
* @return a copy of the main mapping configuration
*/
public AppSchemaDataAccessType getMainMapping() {
AppSchemaDataAccessType mainMapping = cloneMapping(appSchemaMapping);
if (requiresMultipleFiles()) {
// add included types configuration
mainMapping.getIncludedTypes().getInclude()
.add(AppSchemaIO.INCLUDED_TYPES_MAPPING_FILE);
Set<FeatureTypeMapping> toBeRemoved = new HashSet<FeatureTypeMapping>();
Set<FeatureTypeMapping> toBeKept = new HashSet<FeatureTypeMapping>();
groupTypeMappings(toBeKept, toBeRemoved);
purgeTypeMappings(mainMapping, toBeRemoved);
}
return mainMapping;
}
/**
* Returns the mapping configuration for the included types mapping file.
*
* <p>
* If the mapping does not require multiple files, <code>null</code> is
* returned.
* </p>
*
* @return a copy of the included types mapping configuration, or
* <code>null</code>
*/
public AppSchemaDataAccessType getIncludedTypesMapping() {
if (requiresMultipleFiles()) {
AppSchemaDataAccessType includedTypesMapping = cloneMapping(appSchemaMapping);
Set<FeatureTypeMapping> toBeRemoved = new HashSet<FeatureTypeMapping>();
Set<FeatureTypeMapping> toBeKept = new HashSet<FeatureTypeMapping>();
groupTypeMappings(toBeRemoved, toBeKept);
purgeTypeMappings(includedTypesMapping, toBeRemoved);
return includedTypesMapping;
}
else {
return null;
}
}
private void groupTypeMappings(Set<FeatureTypeMapping> mainTypes,
Set<FeatureTypeMapping> includedTypes) {
// look for multiple mappings of the same feature type and determine
// the top level feature type mappings
for (Set<FeatureTypeMapping> ftMappings : featureTypesByTargetElement.values()) {
if (ftMappings.size() > 1) {
FeatureTypeMapping topLevelMapping = null;
for (FeatureTypeMapping m : ftMappings) {
if (topLevelMapping != null) {
// top level mapping already found, drop the others
includedTypes.add(m);
}
else {
if (m.getMappingName() == null || m.getMappingName().trim().isEmpty()) {
// use this as top level mapping
// TODO: there's no guarantee this is the right one
// to pick
topLevelMapping = m;
}
}
}
if (topLevelMapping == null) {
// pick the first one (it's pretty much a random choice)
topLevelMapping = ftMappings.iterator().next();
}
mainTypes.add(topLevelMapping);
}
else {
mainTypes.add(ftMappings.iterator().next());
}
}
// non-feature type mappings go in the "included types" group
for (Set<FeatureTypeMapping> ftMappings : nonFeatureTypesByTargetElement.values()) {
includedTypes.addAll(ftMappings);
}
}
private void purgeTypeMappings(AppSchemaDataAccessType mapping,
Set<FeatureTypeMapping> toBeRemoved) {
Set<String> usedStores = new HashSet<String>();
Iterator<FeatureTypeMapping> featureIt = mapping.getTypeMappings().getFeatureTypeMapping()
.iterator();
while (featureIt.hasNext()) {
FeatureTypeMapping ftMapping = featureIt.next();
if (lookupTypeMapping(ftMapping, toBeRemoved) != null) {
featureIt.remove();
}
else {
usedStores.add(ftMapping.getSourceDataStore());
}
}
// remove unnecessary DataStores
Iterator<DataStore> storeIt = mapping.getSourceDataStores().getDataStore().iterator();
while (storeIt.hasNext()) {
if (!usedStores.contains(storeIt.next().getId())) {
storeIt.remove();
}
}
}
private FeatureTypeMapping lookupTypeMapping(FeatureTypeMapping ftMapping,
Set<FeatureTypeMapping> candidates) {
for (FeatureTypeMapping candidate : candidates) {
boolean sameElement = ftMapping.getTargetElement().equals(candidate.getTargetElement());
boolean noMappingName = ftMapping.getMappingName() == null
&& candidate.getMappingName() == null;
boolean sameMappingName = false;
if (!noMappingName) {
sameMappingName = ftMapping.getMappingName() != null
&& ftMapping.getMappingName().equals(candidate.getMappingName());
}
if (sameElement && (noMappingName || sameMappingName)) {
return candidate;
}
}
return null;
}
static AppSchemaDataAccessType cloneMapping(AppSchemaDataAccessType mapping) {
AppSchemaDataAccessType clone = new AppSchemaDataAccessType();
initMapping(clone);
clone.setCatalog(mapping.getCatalog());
clone.getIncludedTypes().getInclude().addAll(mapping.getIncludedTypes().getInclude());
for (Namespace ns : mapping.getNamespaces().getNamespace()) {
clone.getNamespaces().getNamespace().add(cloneNamespace(ns));
}
for (DataStore ds : mapping.getSourceDataStores().getDataStore()) {
clone.getSourceDataStores().getDataStore().add(cloneDataStore(ds));
}
clone.getTargetTypes().getFeatureType().getSchemaUri()
.addAll(mapping.getTargetTypes().getFeatureType().getSchemaUri());
for (FeatureTypeMapping ftMapping : mapping.getTypeMappings().getFeatureTypeMapping()) {
clone.getTypeMappings().getFeatureTypeMapping().add(cloneFeatureTypeMapping(ftMapping));
}
return clone;
}
static Namespace cloneNamespace(Namespace ns) {
if (ns == null) {
return null;
}
Namespace clone = new Namespace();
clone.setPrefix(ns.getPrefix());
clone.setUri(ns.getUri());
return clone;
}
static DataStore cloneDataStore(DataStore ds) {
DataStore clone = new DataStore();
clone.setParameters(new Parameters());
clone.setId(ds.getId());
clone.setIdAttribute(ds.getIdAttribute());
if (ds.getParameters() != null) {
for (Parameter param : ds.getParameters().getParameter()) {
Parameter paramClone = new Parameter();
paramClone.setName(param.getName());
paramClone.setValue(param.getValue());
clone.getParameters().getParameter().add(paramClone);
}
}
return clone;
}
static FeatureTypeMapping cloneFeatureTypeMapping(FeatureTypeMapping ftMapping) {
FeatureTypeMapping clone = new FeatureTypeMapping();
clone.setAttributeMappings(new AttributeMappings());
if (ftMapping.getAttributeMappings() != null) {
for (AttributeMappingType attrMapping : ftMapping.getAttributeMappings()
.getAttributeMapping()) {
clone.getAttributeMappings().getAttributeMapping()
.add(cloneAttributeMapping(attrMapping));
}
}
clone.setIsDenormalised(ftMapping.isIsDenormalised());
clone.setIsXmlDataStore(ftMapping.isIsXmlDataStore());
clone.setItemXpath(ftMapping.getItemXpath());
clone.setMappingName(ftMapping.getMappingName());
clone.setSourceDataStore(ftMapping.getSourceDataStore());
clone.setSourceType(ftMapping.getSourceType());
clone.setTargetElement(ftMapping.getTargetElement());
return clone;
}
static AttributeMappingType cloneAttributeMapping(AttributeMappingType attrMapping) {
AttributeMappingType clone = new AttributeMappingType();
clone.setEncodeIfEmpty(attrMapping.isEncodeIfEmpty());
clone.setIsList(attrMapping.isIsList());
clone.setIsMultiple(attrMapping.isIsMultiple());
for (ClientProperty clientProp : attrMapping.getClientProperty()) {
ClientProperty clientPropClone = new ClientProperty();
clientPropClone.setName(clientProp.getName());
clientPropClone.setValue(clientProp.getValue());
clone.getClientProperty().add(clientPropClone);
}
clone.setIdExpression(cloneAttributeExpression(attrMapping.getIdExpression()));
clone.setInstancePath(attrMapping.getInstancePath());
clone.setLabel(attrMapping.getLabel());
clone.setParentLabel(attrMapping.getParentLabel());
clone.setSourceExpression(cloneAttributeExpression(attrMapping.getSourceExpression()));
clone.setTargetAttribute(attrMapping.getTargetAttribute());
clone.setTargetAttributeNode(attrMapping.getTargetAttributeNode());
clone.setTargetQueryString(attrMapping.getTargetQueryString());
return clone;
}
static AttributeExpressionMappingType cloneAttributeExpression(
AttributeExpressionMappingType attrExpression) {
if (attrExpression == null) {
return attrExpression;
}
AttributeExpressionMappingType clone = new AttributeExpressionMappingType();
if (attrExpression.getExpression() != null) {
clone.setExpression(new Expression());
// TODO: Expression is xs:anyType, how can I make a copy of it?
clone.getExpression().setExpression(attrExpression.getExpression().getExpression());
}
clone.setIndex(attrExpression.getIndex());
clone.setInputAttribute(attrExpression.getInputAttribute());
clone.setLinkElement(attrExpression.getLinkElement());
clone.setLinkField(attrExpression.getLinkField());
clone.setOCQL(attrExpression.getOCQL());
return clone;
}
/**
* If necessary, initializes fields to minimize the risk of undesired NPEs.
*
* @param mapping the mapping
*/
private static void initMapping(AppSchemaDataAccessType mapping) {
if (mapping.getNamespaces() == null) {
mapping.setNamespaces(new NamespacesPropertyType());
}
if (mapping.getSourceDataStores() == null) {
mapping.setSourceDataStores(new SourceDataStoresPropertyType());
}
if (mapping.getIncludedTypes() == null) {
mapping.setIncludedTypes(new IncludesPropertyType());
}
if (mapping.getTargetTypes() == null) {
mapping.setTargetTypes(new TargetTypesPropertyType());
}
if (mapping.getTargetTypes().getFeatureType() == null) {
mapping.getTargetTypes().setFeatureType(new FeatureType());
}
if (mapping.getTypeMappings() == null) {
mapping.setTypeMappings(new TypeMappingsPropertyType());
}
}
}