package org.geoserver.csw.store.internal; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.lang.StringUtils; import org.geoserver.csw.records.RecordDescriptor; import org.geotools.data.complex.filter.XPathUtil; import org.geotools.factory.CommonFactoryFinder; import org.geotools.filter.text.cql2.CQL; import org.geotools.filter.text.cql2.CQLException; import org.geotools.util.logging.Logging; import org.opengis.filter.FilterFactory; import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.PropertyName; /** * * Catalog Store Mapping * An instance from this class provides a mapping from the data in the Internal Geoserver Catalog to a particular CSW Record Type * * @author Niels Charlier * */ public class CatalogStoreMapping { /** * A Catalog Store Mapping Element, provides mapping of particular attribute. * * @author Niels Charlier * */ public static class CatalogStoreMappingElement { protected String key; protected Expression content = null; protected boolean required = false; protected int splitIndex = -1; /** * Create new Mapping Element * * @param key The Key to be mapped */ protected CatalogStoreMappingElement(String key) { this.key = key; } /** * Getter for mapped key * * @return Mapped Key */ public String getKey() { return key; } /** * Mapper for mapped content expression * * @return content */ public Expression getContent() { return content; } /** * Getter for required property * * @return true if property is required */ public boolean isRequired() { return required; } /** * Getter for splitIndex property * * @return splitIndex */ public int getSplitIndex() { return splitIndex; } } protected static final Logger LOGGER = Logging.getLogger(CatalogStoreMapping.class); protected static final FilterFactory ff = CommonFactoryFinder.getFilterFactory(); protected Map<String, CatalogStoreMappingElement> mappingElements = new HashMap<String, CatalogStoreMappingElement>(); protected CatalogStoreMappingElement identifier = null; protected boolean includeEnvelope = true; /** * Create new Catalog Store Mapping */ protected CatalogStoreMapping() { } /** * Return Collection of all Elements * * @return a Collection with all Mapping Elements */ public final Collection<CatalogStoreMappingElement> elements() { return mappingElements.values(); } /** * Return a mapping element from a mapped key * * @param key the mapped key * @return the element, null if key doesn't exist */ public CatalogStoreMappingElement getElement(String key) { return mappingElements.get(key); } /** * Getter for the identifier element, provides identifier expression for features * * @return the identifier element */ public CatalogStoreMappingElement getIdentifierElement() { return identifier; } /** * Create a submapping from a list of property names * Required properties will also be included. * * @param properties list of property names to be included in submapping * @param rd Record Descriptor * */ public CatalogStoreMapping subMapping(List<PropertyName> properties, RecordDescriptor rd) { Set<String> paths = new HashSet<String>(); for (PropertyName prop : properties) { paths.add(toDotPath(XPathUtil.steps( rd.getFeatureDescriptor() , prop.toString(), rd.getNamespaceSupport()))); } CatalogStoreMapping mapping = new CatalogStoreMapping(); for ( Entry<String, CatalogStoreMappingElement> element : mappingElements.entrySet()) { if (element.getValue().isRequired() || paths.contains(element.getKey())) { mapping.mappingElements.put(element.getKey(), element.getValue()); } } mapping.identifier = identifier; mapping.includeEnvelope = includeEnvelope && paths.contains( rd.getBoundingBoxPropertyName() ); return mapping; } public void setIncludeEnvelope(boolean value) { this.includeEnvelope = value; } public boolean isIncludeEnvelope(){ return includeEnvelope; } /** * Parse a Textual representation of the mapping to create a CatalogStoreMapping * * The textual representation is a set of key-value pairs, where the key represents the mapped key and the value is an OGC expression * representing the mapped content. Furthermore, if the key starts with @ it also defines the ID element and if the key starts with $ * it is a required property. */ public static CatalogStoreMapping parse(Map<String, String> mappingSource) { CatalogStoreMapping mapping = new CatalogStoreMapping(); for (Map.Entry<String, String> mappingEntry : mappingSource.entrySet()) { String key = mappingEntry.getKey(); boolean required = false; boolean id = false; int splitIndex = -1; if("$".equals(key.substring(0,1))) { key = key.substring(1); required = true; } if("@".equals(key.substring(0,1))) { key = key.substring(1); id = true; } if(key.contains("%.")) { splitIndex = StringUtils.countMatches(key.substring(0 , key.indexOf("%.")), ".") ; key = key.replace("%.", "."); } CatalogStoreMappingElement element = mapping.mappingElements.get(key); if (element == null) { element = new CatalogStoreMappingElement(key); mapping.mappingElements.put(key, element); } element.content = parseOgcCqlExpression(mappingEntry.getValue()); element.required = required; element.splitIndex = splitIndex; if (id) { mapping.identifier = element; } } return mapping; } /** * Helper method to parce cql expression * * @param sourceExpr The cql expression string * @return the expression */ protected static Expression parseOgcCqlExpression(String sourceExpr) { Expression expression = Expression.NIL; if (sourceExpr != null && sourceExpr.trim().length() > 0) { try { expression = CQL.toExpression(sourceExpr, ff); } catch (CQLException e) { String formattedErrorMessage = e.getMessage(); LOGGER.log(Level.SEVERE, formattedErrorMessage, e); throw new IllegalArgumentException("Error parsing CQL expression " + sourceExpr + ":\n" + formattedErrorMessage); } catch (Exception e) { e.printStackTrace(); String msg = "parsing expression " + sourceExpr; LOGGER.log(Level.SEVERE, msg, e); throw new IllegalArgumentException(msg + ": " + e.getMessage(), e); } } return expression; } /** * Helper method to convert StepList path to Dot path (separated by dots and no namespace prefixes, used for mapping) * * @param steps XPath steplist * @return String with dot path */ public static String toDotPath(XPathUtil.StepList steps) { StringBuilder sb = new StringBuilder(); for (int i=0; i<steps.size(); i++) { sb.append(steps.get(i).getName().getLocalPart()); sb.append("."); } sb.deleteCharAt(sb.length()-1) ; return sb.toString(); } }