/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2010, Geomatys * * 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.geotoolkit.data; import java.util.Map; import org.apache.sis.feature.builder.FeatureTypeBuilder; import org.apache.sis.storage.DataStoreException; import org.geotoolkit.data.query.Join; import org.geotoolkit.data.query.JoinType; import org.geotoolkit.data.query.Query; import org.geotoolkit.data.query.QueryBuilder; import org.geotoolkit.data.query.QueryUtilities; import org.geotoolkit.data.query.Selector; import org.geotoolkit.data.query.Source; import org.geotoolkit.factory.FactoryFinder; import org.geotoolkit.factory.Hints; import org.geotoolkit.util.NamesExt; import org.opengis.feature.Feature; import org.opengis.feature.FeatureType; import org.opengis.util.GenericName; import org.opengis.filter.Filter; import org.opengis.filter.FilterFactory; import org.opengis.filter.PropertyIsEqualTo; import org.opengis.filter.expression.PropertyName; import org.apache.sis.internal.feature.AttributeConvention; /** * FeatureCollection that takes it'es source from a join query. * * @author Johann Sorel (Geomatys) * @module */ public class DefaultJoinFeatureCollection extends AbstractFeatureCollection{ private static final FilterFactory FF = FactoryFinder.getFilterFactory(null); private final Query query; private final FeatureCollection leftCollection; private final FeatureCollection rightCollection; private FeatureType type = null; private FeatureType leftType = null; private FeatureType rightType = null; private GenericName leftName = null; private GenericName rightName = null; public DefaultJoinFeatureCollection(final String id, final Query query){ super(id,query.getSource()); final Source src = query.getSource(); if(!(src instanceof Join)){ throw new IllegalArgumentException("Query must have a join source."); } if(!QueryUtilities.isAbsolute(src)){ throw new IllegalArgumentException("Query source must be absolute."); } final Join join = (Join) src; this.query = query; //todo must parse the filter on each selector leftCollection = QueryUtilities.evaluate("left", QueryBuilder.all(join.getLeft())); rightCollection = QueryUtilities.evaluate("right", QueryBuilder.all(join.getRight())); } @Override public Join getSource() { return (Join) super.getSource(); } @Override public synchronized FeatureType getFeatureType() { if(type == null){ leftType = leftCollection.getFeatureType(); rightType = rightCollection.getFeatureType(); leftName = (leftCollection.getSource() instanceof Selector) ? NamesExt.valueOf(((Selector)leftCollection.getSource()).getSelectorName()) : leftType.getName(); rightName = (rightCollection.getSource() instanceof Selector) ? NamesExt.valueOf(((Selector)rightCollection.getSource()).getSelectorName()) : rightType.getName(); final FeatureTypeBuilder ftb = new FeatureTypeBuilder(); ftb.addAttribute(String.class).setName(AttributeConvention.IDENTIFIER_PROPERTY).setDefaultValue(""); ftb.addAssociation(leftType).setName(leftName).setMinimumOccurs(0).setMaximumOccurs(1); ftb.addAssociation(rightType).setName(rightName).setMinimumOccurs(0).setMaximumOccurs(1); ftb.setName(leftName.tip().toString()+'-'+rightName.tip().toString()); type = ftb.build(); } return type; } /** * Agregate all feature from selectors to a single complex feature. * * @param candidates * @return aggregated features * @throws DataStoreException */ private Feature toFeature(final Feature left, final Feature right) throws DataStoreException{ final FeatureType type = getFeatureType(); //force creating type. final Feature f = type.newInstance(); String id = ""; if(left != null){ id += left.getPropertyValue(AttributeConvention.IDENTIFIER_PROPERTY.toString()); f.setPropertyValue(leftName.toString(),left); } if(right != null){ if(left!=null) id += " "; id += right.getPropertyValue(AttributeConvention.IDENTIFIER_PROPERTY.toString()); f.setPropertyValue(rightName.toString(),right); } f.setPropertyValue(AttributeConvention.IDENTIFIER_PROPERTY.toString(), id); return f; } @Override public FeatureCollection subCollection(final Query query) throws DataStoreException { final Query combine = QueryUtilities.subQuery(this.query, query); //the result should be an absolute query too. return QueryUtilities.evaluate("sub-"+getID(), combine); } @Override public FeatureIterator iterator(final Hints hints) throws FeatureStoreRuntimeException { final JoinType jt = getSource().getJoinType(); try{ if(jt == JoinType.INNER){ return new JoinInnerRowIterator(null); }else if(jt == JoinType.LEFT_OUTER){ return new JoinOuterRowIterator(true,null); }else if(jt == JoinType.RIGHT_OUTER){ return new JoinOuterRowIterator(false,null); }else{ throw new IllegalArgumentException("Unknowned Join type : " + jt); } }catch(DataStoreException ex){ throw new FeatureStoreRuntimeException(ex); } } @Override public void update(final Filter filter, final Map<String, ?> values) throws DataStoreException { if(isWritable()){ throw new UnsupportedOperationException("Not supported yet."); }else{ throw new DataStoreException("Collection is not writable."); } } @Override public void remove(final Filter filter) throws DataStoreException { if(isWritable()){ throw new UnsupportedOperationException("Not supported yet."); }else{ throw new DataStoreException("Collection is not writable."); } } /** * Iterate on both collections with an Inner join condition. */ private class JoinInnerRowIterator implements FeatureIterator{ private final FeatureIterator leftIterator; private FeatureIterator rightIterator; private Feature leftFeature; private Feature combined; JoinInnerRowIterator(final Hints hints) throws DataStoreException{ leftIterator = leftCollection.iterator(); } @Override public Feature next() { try { searchNext(); } catch (DataStoreException ex) { throw new FeatureStoreRuntimeException(ex); } Feature f = combined; combined = null; return f; } @Override public void close() { leftIterator.close(); if(rightIterator != null){ rightIterator.close(); } } @Override public boolean hasNext() { try { searchNext(); } catch (DataStoreException ex) { throw new FeatureStoreRuntimeException(ex); } return combined != null; } private void searchNext() throws DataStoreException{ if(combined != null) return; final PropertyIsEqualTo equal = getSource().getJoinCondition(); final PropertyName leftProperty = (PropertyName) equal.getExpression1(); final PropertyName rightProperty = (PropertyName) equal.getExpression2(); //we might have several right features for one left if(leftFeature != null && rightIterator != null){ while(combined== null && rightIterator.hasNext()){ final Feature rightRes = rightIterator.next(); combined = checkValid(leftFeature, rightRes); } } if(rightIterator==null && rightIterator != null){ //no more results in right iterator, close iterator rightIterator.close(); rightIterator = null; } while(combined==null && leftIterator.hasNext()){ rightIterator = null; leftFeature = leftIterator.next(); final Object leftValue = leftProperty.evaluate(leftFeature); if(rightIterator == null){ final QueryBuilder qb = new QueryBuilder(); qb.setSource(getSource().getRight()); qb.setFilter(FF.equals(rightProperty, FF.literal(leftValue))); final Query rightQuery = qb.buildQuery(); rightIterator = rightCollection.subCollection(rightQuery).iterator(); } while(combined== null && rightIterator.hasNext()){ final Feature rightRow = rightIterator.next(); combined = checkValid(leftFeature, rightRow); } if(combined==null){ //no more results in right iterator, close iterator rightIterator.close(); rightIterator = null; } } } private Feature checkValid(final Feature left, final Feature right) throws DataStoreException{ final Feature candidate = toFeature(left,right); if(query.getFilter().evaluate(candidate)){ //combine both rows return candidate; }else{ //not a valid combinaison return null; } } @Override public void remove() { throw new UnsupportedOperationException("Not supported yet on join queries."); } } /** * Iterate on both collections with an outer join condition. */ private class JoinOuterRowIterator implements FeatureIterator{ private final FeatureIterator primeIterator; private FeatureIterator secondIterator; private final boolean left; private Feature primeFeature; private Feature nextFeature; JoinOuterRowIterator(final boolean left, final Hints hints) throws DataStoreException{ this.left = left; if(left){ primeIterator = leftCollection.iterator(); }else{ primeIterator = rightCollection.iterator(); } } @Override public Feature next() { try { searchNext(); } catch (DataStoreException ex) { throw new FeatureStoreRuntimeException(ex); } Feature f = nextFeature; nextFeature = null; return f; } @Override public void close() { primeIterator.close(); if(secondIterator != null){ secondIterator.close(); } } @Override public boolean hasNext() { try { searchNext(); } catch (DataStoreException ex) { throw new FeatureStoreRuntimeException(ex); } return nextFeature != null; } private void searchNext() throws DataStoreException{ if(nextFeature != null) return; final PropertyIsEqualTo equal = getSource().getJoinCondition(); final PropertyName leftProperty = (PropertyName) equal.getExpression1(); final PropertyName rightProperty = (PropertyName) equal.getExpression2(); //we might have several right features for one left if(primeFeature != null && secondIterator != null){ while(nextFeature== null && secondIterator.hasNext()){ final Feature secondCandidate = secondIterator.next(); nextFeature = checkValid(primeFeature, secondCandidate, left); } } while(nextFeature==null && primeIterator.hasNext()){ primeFeature = primeIterator.next(); if(secondIterator != null){ secondIterator.close(); secondIterator = null; } final Object primeValue; if(left){ primeValue = leftProperty.evaluate(primeFeature); }else{ primeValue = rightProperty.evaluate(primeFeature); } if(secondIterator == null){ final QueryBuilder qb = new QueryBuilder(); if(left){ qb.setSource(getSource().getRight()); qb.setFilter(FF.equals(rightProperty, FF.literal(primeValue))); secondIterator = rightCollection.subCollection(qb.buildQuery()).iterator(); }else{ qb.setSource(getSource().getLeft()); qb.setFilter(FF.equals(leftProperty, FF.literal(primeValue))); secondIterator = leftCollection.subCollection(qb.buildQuery()).iterator(); } } while(nextFeature==null && secondIterator.hasNext()){ final Feature rightRow = secondIterator.next(); nextFeature = checkValid(primeFeature, rightRow,left); } if(nextFeature == null){ //outer left effect, no right match but still we must return the left side if(left){ nextFeature = toFeature(primeFeature,null); }else{ nextFeature = toFeature(null,primeFeature); } } } } private Feature checkValid(final Feature left, final Feature right, final boolean leftJoin) throws DataStoreException{ final Feature candidate; if(leftJoin){ candidate = toFeature(left,right); }else{ candidate = toFeature(right,left); } if(query.getFilter().evaluate(candidate)){ //combine both rows return candidate; }else{ //not a valid combinaison return null; } } @Override public void remove() { throw new UnsupportedOperationException("Not supported yet on join queries."); } } }