/* (c) 2015 Open Source Geospatial Foundation - all rights reserved * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.wfs; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.xml.namespace.QName; import net.opengis.wfs.XlinkPropertyNameType; import org.geoserver.catalog.FeatureTypeInfo; import org.geoserver.wfs.request.Query; import org.geotools.filter.visitor.DuplicatingFilterVisitor; import org.opengis.feature.type.FeatureType; import org.opengis.feature.type.PropertyDescriptor; import org.opengis.filter.Filter; import org.opengis.filter.expression.PropertyName; import org.opengis.filter.sort.SortBy; /** * Support class checking that the aliases are present, and not in conflict with feature type names * or field names (such conflicts might cause issues down the road). In case of conflicts, the class * will create/alter the aliases to avoid them, and modify filters and property name selection * accordingly * * @author Andrea Aime - GeoSolutions */ class AliasedQuery extends Query { /** * Renames property names following an alias rename map * * @author Andrea Aime - GeoSolutions * */ class AliasRenameVisitor extends DuplicatingFilterVisitor { private Map<String, String> renameMap; public AliasRenameVisitor(Map<String, String> renameMap) { this.renameMap = renameMap; } @Override public Object visit(PropertyName expression, Object extraData) { String name = expression.getPropertyName(); String renamed = rename(renameMap, name); return ff.property(renamed); } } /** * Checks the aliases in the query are present, and not in conflict with feature type names or * field names (such conflicts might cause issues down the road). In case of conflicts a new * Query object will be returned in which the conflicts have been resolved. * * @param metas * @param query * * @throws IOException */ static Query fixAliases(List<FeatureTypeInfo> metas, Query query) throws IOException { Set<String> reservedWords = new HashSet<>(); for (FeatureTypeInfo meta : metas) { reservedWords.add(meta.getName()); reservedWords.add(meta.prefixedName()); FeatureType featureType = meta.getFeatureType(); for (PropertyDescriptor pd : featureType.getDescriptors()) { reservedWords.add(pd.getName().getLocalPart()); reservedWords.add(pd.getName().getURI()); } } // get the starting aliases List<String> aliases; List<String> originalAliases = query.getAliases(); boolean replaced = false; if (query.getAliases() != null && !query.getAliases().isEmpty()) { aliases = new ArrayList<>(query.getAliases()); } else { replaced = true; aliases = new ArrayList<>(); for (int i = 0; i < metas.size(); i++) { aliases.add(String.valueOf((char) ('a' + i))); } } // build replacements if necessary for (int i = 0; i < aliases.size(); i++) { String alias = aliases.get(i); String base = alias; int j = 0; while (reservedWords.contains(alias)) { replaced = true; alias = base + (j++); } aliases.set(i, alias); } if (replaced) { return new AliasedQuery(query, originalAliases, aliases); } else { return query; } } private List<String> aliases; private Query delegate; private Filter filter; private List<String> propertyNames; public AliasedQuery(Query query, List<String> originalAliases, List<String> aliases) { super(null); this.delegate = query; this.aliases = aliases; if (originalAliases != null && !originalAliases.isEmpty()) { Map<String, String> renameMap = buildRenameMap(originalAliases, aliases); this.filter = (Filter) query.getFilter() .accept(new AliasRenameVisitor(renameMap), null); if (query.getPropertyNames() != null) { this.propertyNames = new ArrayList<>(); for (String name : query.getPropertyNames()) { this.propertyNames.add(rename(renameMap, name)); } } } else { this.filter = query.getFilter(); this.propertyNames = query.getPropertyNames(); } } private Map<String, String> buildRenameMap(List<String> originalAliases, List<String> newAliases) { Map<String, String> map = new HashMap<String, String>(); for (int i = 0; i < originalAliases.size(); i++) { String a1 = originalAliases.get(i); String a2 = newAliases.get(i); if (!a1.equals(a2)) { map.put(a1, a2); } } return map; } String rename(Map<String, String> renameMap, String name) { int idx = name.indexOf('/'); if (idx > 0) { String prefix = name.substring(0, idx); String renamed = renameMap.get(prefix); if (renamed != null) { name = renamed + name.substring(idx); } } return name; } @Override public List<QName> getTypeNames() { return delegate.getTypeNames(); } @Override public List<String> getAliases() { return aliases; } @Override public List<String> getPropertyNames() { return propertyNames; } @Override public Filter getFilter() { return filter; } @Override public List<SortBy> getSortBy() { return delegate.getSortBy(); } @Override public List<XlinkPropertyNameType> getXlinkPropertyNames() { return delegate.getXlinkPropertyNames(); } }