/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.feature.retype; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; import org.geoserver.feature.RetypingFeatureCollection; import org.geotools.data.DataAccess; import org.geotools.data.DataSourceException; import org.geotools.data.DataStore; import org.geotools.data.DataUtilities; import org.geotools.data.Query; import org.geotools.data.FeatureLocking; import org.geotools.data.FeatureReader; import org.geotools.data.FeatureStore; import org.geotools.data.FeatureWriter; import org.geotools.data.Join; import org.geotools.data.LockingManager; import org.geotools.data.Query; import org.geotools.data.ServiceInfo; import org.geotools.data.Transaction; import org.geotools.data.simple.SimpleFeatureLocking; import org.geotools.data.simple.SimpleFeatureSource; import org.geotools.data.simple.SimpleFeatureStore; import org.geotools.feature.NameImpl; import org.geotools.feature.simple.SimpleFeatureTypeBuilder; import org.geotools.util.logging.Logging; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.Name; import org.opengis.filter.Filter; /** * A simple data store that can be used to rename feature types (despite the name, the only retyping * considered is the name change, thought it would not be that hard to extend it so that it * could shave off some attribute too) */ public class RetypingDataStore implements DataStore { static final Logger LOGGER = Logging.getLogger(RetypingDataStore.class); private DataStore wrapped; protected volatile Map<String, FeatureTypeMap> forwardMap = new ConcurrentHashMap<String, FeatureTypeMap>(); protected volatile Map<String, FeatureTypeMap> backwardsMap = new ConcurrentHashMap<String, FeatureTypeMap>(); public RetypingDataStore(DataStore wrapped) throws IOException { this.wrapped = wrapped; // force update of type mapping maps getTypeNames(); } public DataStore getWrapped() { return wrapped; } public void createSchema(SimpleFeatureType featureType) throws IOException { throw new UnsupportedOperationException( "GeoServer does not support schema creation at the moment"); } public void updateSchema(String typeName, SimpleFeatureType featureType) throws IOException { throw new UnsupportedOperationException( "GeoServer does not support schema updates at the moment"); } public void removeSchema(String typeName) throws IOException { throw new UnsupportedOperationException( "GeoServer does not support schema removal at the moment"); } public FeatureWriter<SimpleFeatureType, SimpleFeature> getFeatureWriter(String typeName, Filter filter, Transaction transaction) throws IOException { FeatureTypeMap map = getTypeMapBackwards(typeName, true); updateMap(map, false); FeatureWriter<SimpleFeatureType, SimpleFeature> writer = wrapped.getFeatureWriter(map.getOriginalName(), filter, transaction); if (map.isUnchanged()) return writer; return new RetypingFeatureCollection.RetypingFeatureWriter(writer, map.getFeatureType()); } public FeatureWriter<SimpleFeatureType, SimpleFeature> getFeatureWriter(String typeName, Transaction transaction) throws IOException { FeatureTypeMap map = getTypeMapBackwards(typeName, true); updateMap(map, false); FeatureWriter<SimpleFeatureType, SimpleFeature> writer; writer = wrapped.getFeatureWriter(map.getOriginalName(), transaction); if (map.isUnchanged()) return writer; return new RetypingFeatureCollection.RetypingFeatureWriter(writer, map.getFeatureType()); } public FeatureWriter<SimpleFeatureType, SimpleFeature> getFeatureWriterAppend(String typeName, Transaction transaction) throws IOException { FeatureTypeMap map = getTypeMapBackwards(typeName, true); updateMap(map, false); FeatureWriter<SimpleFeatureType, SimpleFeature> writer; writer = wrapped.getFeatureWriterAppend(map.getOriginalName(), transaction); if (map.isUnchanged()) return writer; return new RetypingFeatureCollection.RetypingFeatureWriter(writer, map.getFeatureType()); } public SimpleFeatureType getSchema(String typeName) throws IOException { FeatureTypeMap map = getTypeMapBackwards(typeName, false); if(map == null) throw new IOException("Unknown type " + typeName); updateMap(map, true); return map.getFeatureType(); } public String[] getTypeNames() throws IOException { // here we transform the names, and also refresh the type maps so that // they don't contain stale elements String[] names = wrapped.getTypeNames(); List<String> transformedNames = new ArrayList<String>(); Map<String, FeatureTypeMap> backup = new HashMap<String, FeatureTypeMap>(forwardMap); // Populate local hashmaps with new values. Map<String, FeatureTypeMap> forwardMapLocal = new ConcurrentHashMap<String, FeatureTypeMap>(); Map<String, FeatureTypeMap> backwardsMapLocal = new ConcurrentHashMap<String, FeatureTypeMap>(); for (int i = 0; i < names.length; i++) { String original = names[i]; String transformedName = transformFeatureTypeName(original); if(transformedName != null) { transformedNames.add(transformedName); FeatureTypeMap map = backup.get(original); if (map == null) { map = new FeatureTypeMap(original, transformedName); } forwardMapLocal.put(map.getOriginalName(), map); backwardsMapLocal.put(map.getName(), map); } } // Replace the member variables. forwardMap = forwardMapLocal; backwardsMap = backwardsMapLocal; return (String[]) transformedNames.toArray(new String[transformedNames.size()]); } public FeatureReader<SimpleFeatureType, SimpleFeature> getFeatureReader(Query query, Transaction transaction) throws IOException { FeatureTypeMap map = getTypeMapBackwards(query.getTypeName(), true); updateMap(map, false); FeatureReader<SimpleFeatureType, SimpleFeature> reader; reader = wrapped.getFeatureReader(retypeQuery(query, map), transaction); if (map.isUnchanged()) return reader; return new RetypingFeatureCollection.RetypingFeatureReader(reader, map.getFeatureType(query)); } public SimpleFeatureSource getFeatureSource(String typeName) throws IOException { FeatureTypeMap map = getTypeMapBackwards(typeName, true); updateMap(map, false); SimpleFeatureSource source = wrapped.getFeatureSource(map.getOriginalName()); if (map.isUnchanged()) return source; if (source instanceof FeatureLocking) { SimpleFeatureLocking locking = DataUtilities.simple((FeatureLocking) source); return new RetypingFeatureLocking(this, locking, map); } else if (source instanceof FeatureStore) { SimpleFeatureStore store = DataUtilities.simple((FeatureStore) source); return new RetypingFeatureStore(this, store, map); } return new RetypingFeatureSource(this, source, map); } public LockingManager getLockingManager() { return wrapped.getLockingManager(); } /** * Returns the type map given the external type name * * @param externalTypeName * * @throws IOException */ FeatureTypeMap getTypeMapBackwards(String externalTypeName, boolean checkMap) throws IOException { FeatureTypeMap map = (FeatureTypeMap) backwardsMap.get(externalTypeName); if (map == null && checkMap) throw new IOException("Type mapping has not been established for type " + externalTypeName + ". " + "Make sure you access types using getTypeNames() or getSchema() " + "before trying to read/write onto them"); return map; } /** * Make sure the FeatureTypeMap is fully loaded * * @param map * @throws IOException */ void updateMap(FeatureTypeMap map, boolean forceUpdate) throws IOException { try { if (map.getFeatureType() == null || forceUpdate) { SimpleFeatureType original = wrapped.getSchema(map.getOriginalName()); SimpleFeatureType transformed = transformFeatureType(original); map.setFeatureTypes(original, transformed); } } catch (IOException e) { LOGGER.log(Level.INFO, "Failure to remap feature type " + map.getOriginalName() + ". The type will be ignored", e); // if the feature type cannot be found in the original data store, // remove it from the map backwardsMap.remove(map.getName()); forwardMap.remove(map.getOriginalName()); } } /** * Transforms the original feature type into a destination one according to * the renaming rules. For the moment, it's just a feature type name * replacement * * @param original * * @throws IOException */ protected SimpleFeatureType transformFeatureType(SimpleFeatureType original) throws IOException { String transfomedName = transformFeatureTypeName(original.getTypeName()); if (transfomedName.equals(original.getTypeName())) return original; try { SimpleFeatureTypeBuilder b = new SimpleFeatureTypeBuilder(); b.init(original); b.setName(transfomedName); return b.buildFeatureType(); } catch (Exception e) { throw new DataSourceException("Could not build the renamed feature type.", e); } } /** * Just transform the feature type name, or return null if the original type name is * to be hidden * * @param originalName * */ protected String transformFeatureTypeName(String originalName) { return originalName.replaceAll(":", "_"); } public void dispose() { wrapped.dispose(); } /** * Retypes a query from the extenal type to the internal one using the * provided typemap * @param q * @param typeMap * * @throws IOException */ Query retypeQuery(Query q, FeatureTypeMap typeMap) throws IOException { Query modified = new Query(q); modified.setTypeName(typeMap.getOriginalName()); modified.setFilter(retypeFilter(q.getFilter(), typeMap)); List<Join> joins = q.getJoins(); if(!joins.isEmpty()) { modified.getJoins().clear(); for (Join join : joins) { FeatureTypeMap map = (FeatureTypeMap) backwardsMap.get(join.getTypeName()); if(map == null) { // nothing we can do about it modified.getJoins().add(join); } else { final FeatureTypeMap joinTypeMap = getTypeMapBackwards(join.getTypeName(), true); String originalName = joinTypeMap.getOriginalName(); Join mj = new Join(originalName, join.getJoinFilter()); mj.setType(join.getType()); mj.setAlias(join.getAlias()); mj.setProperties(join.getProperties()); mj.setFilter(join.getFilter()); modified.getJoins().add(mj); } } } return modified; } /** * Retypes a filter making sure the fids are using the internal typename prefix * @param filter * @param typeMap * */ Filter retypeFilter(Filter filter, FeatureTypeMap typeMap) { FidTransformeVisitor visitor = new FidTransformeVisitor(typeMap); return (Filter) filter.accept(visitor, null); } public ServiceInfo getInfo() { return wrapped.getInfo(); } /** * Delegates to {@link #getFeatureSource(String)} with * {@code name.getLocalPart()} * * @since 2.5 * @see DataAccess#getFeatureSource(Name) */ public SimpleFeatureSource getFeatureSource(Name typeName) throws IOException { return getFeatureSource(typeName.getLocalPart()); } /** * Returns the same list of names than {@link #getTypeNames()} meaning the * returned Names have no namespace set. * * @since 1.7 * @see DataAccess#getNames() */ public List<Name> getNames() throws IOException { String[] typeNames = getTypeNames(); List<Name> names = new ArrayList<Name>(typeNames.length); for (String typeName : typeNames) { names.add(new NameImpl(typeName)); } return names; } /** * Delegates to {@link #getSchema(String)} with {@code name.getLocalPart()} * * @since 1.7 * @see DataAccess#getSchema(Name) */ public SimpleFeatureType getSchema(Name name) throws IOException { return getSchema(name.getLocalPart()); } /** * Delegates to {@link #updateSchema(String, SimpleFeatureType)} with * {@code name.getLocalPart()} * * @since 1.7 * @see DataAccess#getFeatureSource(Name) */ public void updateSchema(Name typeName, SimpleFeatureType featureType) throws IOException { updateSchema(typeName.getLocalPart(), featureType); } /** * Delegates to {@link #removeSchema(String)} with {@code name.getLocalPart()} * */ public void removeSchema(Name typeName) throws IOException { removeSchema(typeName.getLocalPart()); } }