/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-2011, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.jdbc;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.geotools.data.Join;
import org.geotools.data.Query;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.filter.visitor.DuplicatingFilterVisitor;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.filter.Filter;
import org.opengis.filter.expression.PropertyName;
/**
* Holds information about a join query.
*
* @author Justin Deoliveira, OpenGeo
*
*/
public class JoinInfo {
public static JoinInfo create(Query query, JDBCFeatureSource featureSource) throws IOException {
return create(query, featureSource.getSchema(), featureSource.getDataStore());
}
public static JoinInfo create(Query query, SimpleFeatureType featureType, JDBCDataStore dataStore)
throws IOException {
JoinInfo info = new JoinInfo();
info.setPrimaryAlias("a");
for (int i = 0; i < query.getJoins().size(); i++) {
Join j = query.getJoins().get(i);
JoinPart part = new JoinPart(j);
info.getParts().add(part);
//load the feature type being joined to
JDBCFeatureSource joinFeatureSource = dataStore.getAbsoluteFeatureSource(j.getTypeName());
part.setFeatureSource(joinFeatureSource);
//ensure every join as a unique alias
String alias = String.valueOf((char)('b' + i));
part.setAlias(alias);
//hack on the join filter as necessary
Filter joinFilter = j.getJoinFilter();
if (query.getAlias() != null) {
//rewrite any user specified alias with the one we specified
joinFilter =
(Filter) joinFilter.accept(new JoinPrefixRewriter(query.getAlias(), "a"), null);
}
if (j.getAlias() != null) {
//rewrite any user specified alias with the one we specified
joinFilter =
(Filter) joinFilter.accept(new JoinPrefixRewriter(j.getAlias(), alias), null);
}
//qualify all property names in the join filter so that they known about their
// feature type and alias
joinFilter = (Filter) joinFilter.accept(new JoinQualifier(featureType, "a",
joinFeatureSource.getSchema(), alias), null);
part.setJoinFilter(joinFilter);
//split the other filter
Filter[] prePostFilters = joinFeatureSource.splitFilter(j.getFilter());
//build the query and return feature types based on the post filter
SimpleFeatureType[] types = joinFeatureSource.buildQueryAndReturnFeatureTypes(
joinFeatureSource.getSchema(), j.getPropertyNames(), prePostFilters[1]);
//alias any attributes in this feature type that clash with attributes in the primary
// feature type
types[0] = SimpleFeatureTypeBuilder.copy(types[0]);
for (AttributeDescriptor att : types[0].getAttributeDescriptors()) {
if (featureType.getDescriptor(att.getName()) != null) {
att.getUserData().put(
JDBCDataStore.JDBC_COLUMN_ALIAS, alias + "_" + att.getLocalName());
}
}
part.setQueryFeatureType(types[0]);
part.setReturnFeatureType(types[1]);
//qualify the pre filter
if (prePostFilters[0] != null && prePostFilters[0] != Filter.INCLUDE) {
prePostFilters[0] = (Filter) prePostFilters[0].accept(
new JoinQualifier(joinFeatureSource.getSchema(), alias), null);
}
part.setPreFilter(prePostFilters[0]);
part.setPostFilter(prePostFilters[1]);
//assign an attribute name in the resulting feature type
//TODO: we should check to ensure that the joined feature type attribute name are
// actually unique
part.setAttributeName(part.getJoin().getAlias() != null ?
part.getJoin().getAlias() : part.getQueryFeatureType().getTypeName());
}
//qualify the main query filter
Filter filter = query.getFilter();
if (filter != null && !Filter.INCLUDE.equals(filter)) {
filter = (Filter) filter.accept(new JoinQualifier(featureType, "a"), null);
}
info.setFilter(filter);
return info;
}
/** primary table alias */
String primaryAlias;
/** parts of the join */
List<JoinPart> parts = new ArrayList();
/** the "joinified" filter of the main query */
Filter filter;
private JoinInfo() {
}
public String getPrimaryAlias() {
return primaryAlias;
}
public void setPrimaryAlias(String primaryAlias) {
this.primaryAlias = primaryAlias;
}
public Filter getFilter() {
return filter;
}
public void setFilter(Filter filter) {
this.filter = filter;
}
public boolean hasPostFilters() {
for (JoinPart p : parts) {
if (p.getPostFilter() != null && !Filter.INCLUDE.equals(p.getPostFilter())) {
return true;
}
}
return false;
}
public List<JoinPart> getParts() {
return parts;
}
public static class JoinPart {
/** original join object */
Join join;
/** assigned alias */
String alias;
/** join filter */
Filter joinFilter;
/** feature source being joined to */
JDBCFeatureSource featureSource;
/** query feature type */
SimpleFeatureType queryFeatureType;
/** join feature type */
SimpleFeatureType returnFeatureType;
/** pre filter */
Filter preFilter;
/** post filter */
Filter postFilter;
/** the attribute in the final feature type this part is assigned */
String attributeName;
public JoinPart(Join join) {
this.join = join;
}
public Join getJoin() {
return join;
}
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public Filter getJoinFilter() {
return joinFilter;
}
public void setJoinFilter(Filter joinFilter) {
this.joinFilter = joinFilter;
}
public JDBCFeatureSource getFeatureSource() {
return featureSource;
}
public void setFeatureSource(JDBCFeatureSource featureSource) {
this.featureSource = featureSource;
}
public SimpleFeatureType getQueryFeatureType() {
return queryFeatureType;
}
public void setQueryFeatureType(SimpleFeatureType queryFeatureType) {
this.queryFeatureType = queryFeatureType;
}
public SimpleFeatureType getReturnFeatureType() {
return returnFeatureType;
}
public void setReturnFeatureType(SimpleFeatureType returnFeatureType) {
this.returnFeatureType = returnFeatureType;
}
public Filter getPreFilter() {
return preFilter;
}
public void setPreFilter(Filter preFilter) {
this.preFilter = preFilter;
}
public Filter getPostFilter() {
return postFilter;
}
public void setPostFilter(Filter postFilter) {
this.postFilter = postFilter;
}
public String getAttributeName() {
return attributeName;
}
public void setAttributeName(String attributeName) {
this.attributeName = attributeName;
}
}
static class JoinPrefixRewriter extends DuplicatingFilterVisitor {
String from, to;
public JoinPrefixRewriter(String from, String to) {
this.from = from;
this.to = to;
}
@Override
public Object visit(PropertyName expression, Object extraData) {
String name = expression.getPropertyName();
if (name.startsWith(from+".")) {
name = to + "." + name.substring((from+".").length());
}
return getFactory(extraData).property(name, expression.getNamespaceContext());
}
}
static class JoinQualifier extends DuplicatingFilterVisitor {
SimpleFeatureType ft1, ft2;
String alias1, alias2;
public JoinQualifier(SimpleFeatureType ft, String alias) {
this(ft, alias, null, null);
}
public JoinQualifier(SimpleFeatureType ft1, String alias1, SimpleFeatureType ft2, String alias2) {
this.ft1 = ft1;
this.ft2 = ft2;
this.alias1 = alias1;
this.alias2 = alias2;
}
@Override
public Object visit(PropertyName expression, Object extraData) {
String name = expression.getPropertyName();
String[] split = name.split("\\.");
//if split.length > 2 then join up remaining parts, means the column name itself had a
// period in it
if (split.length > 2) {
String prefix = split[0];
StringBuffer sb = new StringBuffer();
for (int i = 1; i < split.length; i++) {
sb.append(split[i]);
}
split = new String[]{prefix, sb.toString()};
}
JoinPropertyName propertyName = null;
//if we only have one feature type its easy, use the first feature type
if (ft2 == null) {
propertyName = new JoinPropertyName(ft1, alias1, split.length > 1 ? split[1] : split[0]);
}
else {
if (split.length == 1) {
//name was unprefixed, figure out what feature type the meant
SimpleFeatureType ft = ft1.getDescriptor(split[0]) != null ? ft1 :
ft2.getDescriptor(split[0]) != null ? ft2 : null;
if (ft == null) {
throw new IllegalArgumentException(String.format("Attribute '%s' not present in"
+ " either type '%s' or '%s'", split[0], ft1.getTypeName(), ft2.getTypeName()));
}
propertyName = new JoinPropertyName(ft, ft == ft1 ? alias1 : alias2, split[0]);
}
else {
//name was prefixed, look up the type based on prefix
SimpleFeatureType ft = split[0].equals(alias1) ? ft1 :
split[0].equals(alias2) ? ft2 : null;
if (ft == null) {
throw new IllegalArgumentException(String.format("Prefix '%s' does not match " +
"either alias '%s' or '%s'", split[0], alias1, alias2));
}
propertyName = new JoinPropertyName(ft, split[0], split[1]);
}
}
return propertyName;
}
}
}