/* (c) 2014 Open Source Geospatial Foundation - all rights reserved * (c) 2012 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.csw.records; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.geoserver.csw.records.RecordBuilder; import org.geotools.feature.AttributeBuilder; import org.geotools.feature.ComplexFeatureBuilder; import org.geotools.feature.LenientFeatureFactoryImpl; import org.geotools.feature.type.Types; import org.geotools.geometry.jts.JTS; import org.geotools.geometry.jts.ReferencedEnvelope; import org.opengis.feature.Attribute; import org.opengis.feature.ComplexAttribute; import org.opengis.feature.Feature; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.feature.type.ComplexType; import org.opengis.feature.type.Name; import org.opengis.feature.type.PropertyDescriptor; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.Polygon; /** * A generic helper that builds CSW records as GeoTools features * * @author Niels Charlier */ public class GenericRecordBuilder implements RecordBuilder { /** * A user property of the boundingBox attribute containing the original envelopes * of the Record. */ public static final String ORIGINAL_BBOXES = "RecordOriginalBounds"; protected ComplexFeatureBuilder fb ; protected AttributeBuilder ab = new AttributeBuilder(new LenientFeatureFactoryImpl()); protected List<ReferencedEnvelope> boxes = new ArrayList<ReferencedEnvelope>(); protected RecordDescriptor recordDescriptor; protected Map<Name, Name> substitutionMap = new HashMap<Name, Name>(); /** * A tree structure is built initially before the feature is built, so that all data for the feature can be collected * and properly structured to build the feature. * * */ protected static abstract class TreeNode { AttributeDescriptor descriptor = null; public abstract TreeNode clone(); } protected static abstract class TreeLeaf extends TreeNode { public Map<Object, Object> userData; } protected static class ComplexTreeLeaf extends TreeLeaf { public Map<String, Object> value = new HashMap<String, Object>(); @Override public ComplexTreeLeaf clone(){ ComplexTreeLeaf leaf = new ComplexTreeLeaf(); leaf.value.putAll(value); leaf.userData = userData; leaf.descriptor = descriptor; return leaf; } } protected static class SimpleTreeLeaf extends TreeLeaf { public Object value; @Override public SimpleTreeLeaf clone(){ SimpleTreeLeaf leaf = new SimpleTreeLeaf(); leaf.value = value; leaf.userData.putAll(userData); leaf.descriptor = descriptor; return leaf; } } protected static class TreeBranch extends TreeNode { public Map<String, List<TreeNode>> children = new HashMap<String, List<TreeNode>>(); @Override public TreeBranch clone(){ TreeBranch branch = new TreeBranch(); for (Map.Entry<String, List<TreeNode>> pair : children.entrySet()) { List<TreeNode> list = new ArrayList<TreeNode>(); for (TreeNode node : pair.getValue()){ list.add(node.clone()); } } branch.descriptor = descriptor; return branch; } } /** * The root of the tree */ protected TreeBranch root = new TreeBranch(); /** * Start Generic Record Builder based on the Record Descriptor * * @param recordDescriptor The Record Descriptor */ public GenericRecordBuilder(RecordDescriptor recordDescriptor) { this.recordDescriptor = recordDescriptor; fb = new ComplexFeatureBuilder(recordDescriptor.getFeatureDescriptor()); for (PropertyDescriptor descriptor : recordDescriptor.getFeatureType().getDescriptors()) { @SuppressWarnings("unchecked") List<AttributeDescriptor> substitutionGroup = (List<AttributeDescriptor>) descriptor.getUserData().get("substitutionGroup"); if (substitutionGroup != null) { for (Iterator<AttributeDescriptor> it = substitutionGroup.iterator(); it.hasNext();) { substitutionMap.put(it.next().getName(), descriptor.getName()); } } substitutionMap.put(descriptor.getName(), descriptor.getName()); } } /** * Helper method for creating attributes in the tree structure * * @param branch * @param index * @param type * @param path * @param value * @param userData */ private void createAttribute(TreeBranch branch, int index, ComplexType type, String[] path, List<Object> value, Map<Object, Object> userData, int splitIndex) { AttributeDescriptor descriptor = (AttributeDescriptor) Types.findDescriptor(type, path[index]); if (descriptor == null) { throw new IllegalArgumentException("Cannot find descriptor for attribute " + path[index] + " in type " + type.getName().toString()); } List<TreeNode> treenodes = branch.children.get(path[index]); if (treenodes == null) { treenodes = new ArrayList<TreeNode>(); branch.children.put(path[index], treenodes); } if (index == path.length - 1) { //can only happen if there is a path with size 1 for (Object item : value) { SimpleTreeLeaf leaf = new SimpleTreeLeaf(); leaf.userData = userData; leaf.descriptor = descriptor; leaf.value = item; leaf.userData = userData; treenodes.add(leaf); } } else if (index == path.length - 2) { if (treenodes.isEmpty()) { for (int i = 0; i<value.size(); i++) { ComplexTreeLeaf leaf = new ComplexTreeLeaf(); treenodes.add(leaf); leaf.descriptor = descriptor; } } else if (treenodes.size() == 1) { for (int i = 1; i<value.size(); i++) { treenodes.add(treenodes.get(0).clone()); } } else if (value.size()!=1 && treenodes.size() != value.size()) { throw new IllegalArgumentException("Error in mapping: Number of values not matching."); } for (int i = 0; i<value.size(); i++) { ComplexTreeLeaf leaf = (ComplexTreeLeaf) treenodes.get(i); leaf.value.put(path[index+1], value.size()==1? value.get(0) : value.get(i)); leaf.userData = userData; } } else { if (index != splitIndex) { if (treenodes.isEmpty()) { TreeNode child = new TreeBranch(); child.descriptor = descriptor; treenodes.add(child); } for (int i = 0; i < treenodes.size(); i++) { createAttribute((TreeBranch) treenodes.get(i), index+1, (ComplexType) descriptor.getType(), path, value, userData, splitIndex); } } else { if (treenodes.isEmpty()) { for (int i = 0; i<value.size(); i++) { TreeNode child = new TreeBranch(); child.descriptor = descriptor; treenodes.add(child); } } else if (treenodes.size() == 1) { for (int i = 1; i < value.size(); i++) { treenodes.add(treenodes.get(0).clone()); } } else if (treenodes.size() != value.size()) { throw new IllegalArgumentException("Error in mapping: Number of values not matching."); } for (int i = 0; i < value.size(); i++) { createAttribute((TreeBranch) treenodes.get(i), index+1, (ComplexType) descriptor.getType(), path, Collections.singletonList(value.get(i)), userData, splitIndex); } } } } /** * Adds an element to the current record with user data * * @param name path of property with dots * @param value the value(s) to be inserted * @param userData the user data */ public void addElement(String name, List<Object> value, Map<Object, Object> userData, int splitIndex) { createAttribute(root, 0, recordDescriptor.getFeatureType(), name.split("\\."), value, userData, splitIndex); } /** * Adds an element to the current record * * @param name path of property with dots * @param values the value(s) to be inserted */ public void addElement(String name, String... values) { addElement(name, Arrays.asList((Object[]) values), null, -1); } /** * Adds an element to the current record * * @param name path of property with dots * @param values the value(s) to be inserted */ public void addElement(String name, int splitIndex, String... values) { addElement(name, Arrays.asList((Object[]) values), null, splitIndex); } /** * Adds a bounding box to the record. The envelope must be in WGS84 * * @param env the bbox */ public void addBoundingBox(ReferencedEnvelope env) { boxes.add(env); } /** * Builds a record and sets up to work on the next one * * @param id record id * @return the Feature */ public Feature build(String id) { // gather all the bounding boxes in a single geometry Geometry geom = null; for (ReferencedEnvelope env : boxes) { try { env = env.transform(AbstractRecordDescriptor.DEFAULT_CRS, true); Polygon poly = JTS.toGeometry(env); poly.setUserData(AbstractRecordDescriptor.DEFAULT_CRS); if (geom == null) { geom = poly; } else { geom = geom.union(poly); } } catch (Exception e) { throw new IllegalArgumentException( "Failed to reproject one of the bounding boxes to WGS84, " + "this should never happen with valid coordinates", e); } } if (geom instanceof Polygon) { geom = geom.getFactory().createMultiPolygon(new Polygon[] { (Polygon) geom }); } Map<Object, Object> userData = Collections.singletonMap((Object) ORIGINAL_BBOXES, (Object) new ArrayList<ReferencedEnvelope>(boxes)); addElement( recordDescriptor.getBoundingBoxPropertyName(), Collections.singletonList((Object)geom), userData, -1); for (List<TreeNode> nodes : root.children.values()) { for (TreeNode node : nodes) { Attribute att = buildNode(node); fb.append(substitutionMap.get(att.getName()), att); } } boxes.clear(); root = new TreeBranch(); return fb.buildFeature(id); } /** * Helper method for building feature from tree node * * @param node the node * @return list of attributes to be added to feature */ private Attribute buildNode(TreeNode node) { if (node instanceof TreeLeaf) { if (node instanceof ComplexTreeLeaf) { ComplexTreeLeaf leaf = (ComplexTreeLeaf) node; ComplexType type = (ComplexType) node.descriptor.getType(); ab.setDescriptor(node.descriptor); for (Entry<String, Object> entry : leaf.value.entrySet()) { PropertyDescriptor descriptor = Types.findDescriptor(type, entry.getKey()); if (descriptor == null) { throw new IllegalArgumentException("Cannot find descriptor for attribute " + entry.getKey() + " in type " + type.getName().toString()); } ab.add(null, entry.getValue(), descriptor.getName()); } Attribute att = ab.build(); if (leaf.userData != null) { for (Entry<String, Object> entry : leaf.value.entrySet()) { ((ComplexAttribute) att).getProperty(entry.getKey()).getUserData() .putAll(leaf.userData); } } return att; } else { SimpleTreeLeaf leaf = (SimpleTreeLeaf)node; ab.setDescriptor(node.descriptor); Attribute att = ab.buildSimple(null, leaf.value); if (leaf.userData != null) { att.getUserData().putAll(leaf.userData); } return att; } } else if (node instanceof TreeBranch) { List<Attribute> list = new ArrayList<Attribute>(); for (List<TreeNode> nodes : ((TreeBranch)node).children.values()) { for (TreeNode child : nodes) { list.add(buildNode(child)); } } return ab.createComplexAttribute(list, null, node.descriptor, null); } return null; } }