/*************************************************** * * cismet GmbH, Saarbruecken, Germany * * ... and it just works. * ****************************************************/ /* * To change this template, choose Tools | Templates * and open the template in the editor. */ package de.cismet.cismap.commons.featureservice.factory; import com.vividsolutions.jts.geom.Geometry; import groovy.lang.GroovyShell; import org.apache.log4j.Logger; import org.deegree.style.se.unevaluated.Style; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Vector; import javax.swing.SwingWorker; import de.cismet.cismap.commons.Debug; import de.cismet.cismap.commons.features.FeatureServiceFeature; import de.cismet.cismap.commons.featureservice.*; /** * Abstract impelementation of a FeatureFactory. Supports re-evaluation of id and annotation expressions. * * @author Pascal Dihé * @version $Revision$, $Date$ */ public abstract class AbstractFeatureFactory<FT extends FeatureServiceFeature, QT> implements FeatureFactory<FT, QT> { //~ Static fields/initializers --------------------------------------------- public static final boolean DEBUG = Debug.DEBUG; //~ Instance fields -------------------------------------------------------- public String layerName = null; // -1 = not id available protected int ID = -1; protected Logger logger = Logger.getLogger(this.getClass()); protected LayerProperties layerProperties; protected int maxFeatureCount = -1; protected GroovyShell groovyShell = null; // protected boolean idExpressionChanged = true; // protected boolean primaryAnnotationExpressionChanged = true; // protected boolean secondaryAnnotationExpressionChanged = true; protected Vector<FT> lastCreatedfeatureVector = new Vector(); // private BoundingBox lastBB = null; // private BoundingBox diff = null; // private final WKTReader reader; protected Map<String, LinkedList<Style>> styles; protected Geometry lastGeom = null; protected QT lastQuery; private volatile boolean isInterruptedAllowed = true; //~ Constructors ----------------------------------------------------------- /** * Creates a new AbstractFeatureFactory object. */ protected AbstractFeatureFactory() { } /** * Creates a new AbstractFeatureFactory object. * * @param aff DOCUMENT ME! */ protected AbstractFeatureFactory(final AbstractFeatureFactory aff) { this.ID = aff.ID; this.layerProperties = aff.layerProperties.clone(); this.maxFeatureCount = aff.maxFeatureCount; this.lastCreatedfeatureVector = new Vector(aff.lastCreatedfeatureVector.size()); this.lastCreatedfeatureVector.addAll(lastCreatedfeatureVector); this.styles = aff.styles; } //~ Methods ---------------------------------------------------------------- /** * DOCUMENT ME! */ public synchronized void waitUntilInterruptedIsAllowed() { try { if (!isInterruptedAllowed) { wait(); } } catch (InterruptedException e) { logger.error("should never happen"); } } /** * DOCUMENT ME! */ protected synchronized void setInterruptedAllowed() { isInterruptedAllowed = true; notifyAll(); } /** * DOCUMENT ME! */ protected synchronized void setInterruptedNotAllowed() { isInterruptedAllowed = false; } @Override public void setLayerName(final String layerName) { this.layerName = layerName; } @Override public void setSLDStyle(final Map<String, LinkedList<Style>> styles) { this.styles = styles; for (final FT feature : lastCreatedfeatureVector) { feature.setSLDStyles(getStyle(layerName)); } } @Override public void setLayerProperties(final LayerProperties layerProperties) { final LayerProperties oldLayerProperties = this.layerProperties; this.layerProperties = layerProperties; if (this.isGenerateIds() && this.layerProperties.isIdExpressionEnabled()) { logger.warn( "factory supports automatic id generation, disabling id expression support in layer properties"); this.layerProperties.setIdExpressionEnabled(false); } if (this.lastCreatedfeatureVector.size() > 0) { final long start = System.currentTimeMillis(); if (logger.isDebugEnabled()) { logger.debug(this.lastCreatedfeatureVector.size() + " last created features found, applying updated expressions if applicable"); } // check if at least one expression changed if (((oldLayerProperties.getIdExpression() == null) || oldLayerProperties.getIdExpression().equals(this.layerProperties.getIdExpression())) && ((oldLayerProperties.getPrimaryAnnotationExpression() == null) || oldLayerProperties.getPrimaryAnnotationExpression().equals( this.layerProperties.getPrimaryAnnotationExpression())) && ((oldLayerProperties.getSecondaryAnnotationExpression() == null) || oldLayerProperties.getSecondaryAnnotationExpression().equals( this.layerProperties.getSecondaryAnnotationExpression()))) { if (logger.isDebugEnabled()) { logger.debug("expressions did not change, re-elevation not neccessary"); } for (final FT feature : this.lastCreatedfeatureVector) { feature.setLayerProperties(this.layerProperties); } } else if ((this.layerProperties.getIdExpressionType() == LayerProperties.EXPRESSIONTYPE_UNDEFINED) && (this.layerProperties.getPrimaryAnnotationExpressionType() == LayerProperties.EXPRESSIONTYPE_UNDEFINED) && (this.layerProperties.getSecondaryAnnotationExpressionType() == LayerProperties.EXPRESSIONTYPE_UNDEFINED)) { if (logger.isDebugEnabled()) { logger.debug("re-evaluation not necessary, no supported expressions"); } for (final FT feature : this.lastCreatedfeatureVector) { feature.setLayerProperties(this.layerProperties); } } else { this.reEvaluteExpressions(this.lastCreatedfeatureVector, null); } if (logger.isDebugEnabled()) { logger.debug("updating layer properties of " + this.lastCreatedfeatureVector.size() + " features took " + (System.currentTimeMillis() - start) + " ms"); } } else { logger.warn("no last created features that could be refreshed found"); } } @Override public LayerProperties getLayerProperties() { return this.layerProperties; } @Override public int getMaxFeatureCount() { return this.maxFeatureCount; } @Override public void setMaxFeatureCount(final int maxFeatureCount) { this.maxFeatureCount = maxFeatureCount; } /** * DOCUMENT ME! * * @param featureList DOCUMENT ME! * @param attributes DOCUMENT ME! */ protected void sortFeatureList(final List<? extends FeatureServiceFeature> featureList, final FeatureServiceAttribute[] attributes) { Collections.sort(featureList, new Comparator<FeatureServiceFeature>() { @Override public int compare(final FeatureServiceFeature o1, final FeatureServiceFeature o2) { for (final FeatureServiceAttribute attribute : attributes) { final Object att1 = o1.getProperty(attribute.getName()); final Object att2 = o2.getProperty(attribute.getName()); if ((att1 instanceof Comparable) && (att2 instanceof Comparable)) { final Comparable c1 = (Comparable)att1; final Comparable c2 = (Comparable)att2; final int result = c1.compareTo(c2); if (result != 0) { return result; } } } return 0; } }); } /** * Re-evaluates the expressions of all features in the list. This operation may be called, if new layer properties * are applied on cached features. * * @param features list of caches features * @param workerThread DOCUMENT ME! */ protected void reEvaluteExpressions(final List<FT> features, final SwingWorker workerThread) { if (logger.isDebugEnabled()) { logger.debug("SW[" + workerThread + "]: performing re-evaluation of the expressions of " + features.size() + " selected features"); } final long start = System.currentTimeMillis(); int i = 0; for (final FT feature : features) { // check if thread is canceled ......................................... if (this.checkCancelled(workerThread, " evaluating expression")) { return; } // check if thread is canceled ......................................... feature.setLayerProperties(this.layerProperties); this.evaluateExpressions(feature, i); i++; } if (logger.isDebugEnabled()) { logger.debug("SW[" + workerThread + "]: re-evaluation of " + features.size() + " features took " + (System.currentTimeMillis() - start) + " ms"); } } /** * Evaluates id an annotation expressions of the current layer properties and applies it to the feature. * * @param feature to on that the expressions are applied * @param index is used as if the evaluation of the id expression fails */ protected void evaluateExpressions(final FT feature, final int index) { Object property = null; String id = null; // ID Expression ........................................................... if (!this.isGenerateIds()) { if (DEBUG) { if (logger.isDebugEnabled()) { logger.debug("evaluating idExpression '" + this.layerProperties.getIdExpression() + "' of type " + this.layerProperties.getIdExpressionType()); } } switch (this.layerProperties.getIdExpressionType()) { case LayerProperties.EXPRESSIONTYPE_PROPERTYNAME: { if (DEBUG) { if (logger.isDebugEnabled()) { logger.debug("evaluating idExpression: EXPRESSIONTYPE_PROPERTYNAME " + LayerProperties.EXPRESSIONTYPE_PROPERTYNAME); } } property = feature.getProperty(this.layerProperties.getIdExpression()); try { if (property != null) { if (DEBUG) { if (logger.isDebugEnabled()) { logger.debug("evaluating idExpression: property '" + property + "'"); } } feature.setId(Integer.parseInt(property.toString())); } else { feature.setId(ID); if (DEBUG) { logger.warn("evaluating idExpression: property '" + this.layerProperties.getIdExpression() + "' not found, setting id to " + ID); } } } catch (NumberFormatException nfe) { feature.setId(ID); if (DEBUG) { logger.warn("evaluating idExpression: property '" + property.toString() + "' could not be converted to int, setting id to " + ID); } } break; } case LayerProperties.EXPRESSIONTYPE_BEANSHELL: { if (DEBUG) { if (logger.isDebugEnabled()) { logger.debug("evaluating idExpression: EXPRESSIONTYPE_BEANSHELL " + LayerProperties.EXPRESSIONTYPE_BEANSHELL); } } id = this.evaluateBeanShellExpression(feature, this.layerProperties.getIdExpression()); try { if (id != null) { feature.setId(Integer.parseInt(id.toString())); } else { feature.setId(ID); } } catch (NumberFormatException nfe) { feature.setId(ID); } break; } case LayerProperties.EXPRESSIONTYPE_GROOVY: { if (DEBUG) { if (logger.isDebugEnabled()) { logger.debug("evaluating idExpression: EXPRESSIONTYPE_GROOVY " + LayerProperties.EXPRESSIONTYPE_GROOVY); } } id = this.evaluateGroovyExpressions(feature, this.layerProperties.getIdExpression()); try { if (id != null) { feature.setId(Integer.parseInt(id.toString())); } else { feature.setId(ID); } } catch (NumberFormatException nfe) { feature.setId(ID); } break; } default: { feature.setId(ID); break; } } } // PrimaryAnnotationExpression ............................................. if (DEBUG) { if (logger.isDebugEnabled()) { logger.debug("evaluating PrimaryAnnotationExpression '" + this.layerProperties.getPrimaryAnnotationExpression() + "' of type " + this.layerProperties.getPrimaryAnnotationExpressionType()); } } switch (this.layerProperties.getPrimaryAnnotationExpressionType()) { case LayerProperties.EXPRESSIONTYPE_STATIC: { feature.setPrimaryAnnotation(this.layerProperties.getPrimaryAnnotationExpression()); break; } case LayerProperties.EXPRESSIONTYPE_PROPERTYNAME: { property = feature.getProperty(this.layerProperties.getPrimaryAnnotationExpression()); if (DEBUG) { if (logger.isDebugEnabled()) { logger.debug("evaluating PrimaryAnnotationExpression: setting PrimaryAnnotationExpression '" + property + "'"); } } if (property != null) { feature.setPrimaryAnnotation(property.toString()); } break; } case LayerProperties.EXPRESSIONTYPE_BEANSHELL: { feature.setPrimaryAnnotation(this.evaluateBeanShellExpression( feature, this.layerProperties.getPrimaryAnnotationExpression())); break; } case LayerProperties.EXPRESSIONTYPE_GROOVY: { feature.setPrimaryAnnotation(this.evaluateGroovyExpressions( feature, this.layerProperties.getPrimaryAnnotationExpression())); break; } } // SecondaryAnnotationExpression ........................................... if (DEBUG) { if (logger.isDebugEnabled()) { logger.debug("evaluating SecondaryAnnotationExpression '" + this.layerProperties.getSecondaryAnnotationExpression() + "' of type " + this.layerProperties.getSecondaryAnnotationExpressionType()); } } switch (this.layerProperties.getSecondaryAnnotationExpressionType()) { case LayerProperties.EXPRESSIONTYPE_STATIC: { feature.setSecondaryAnnotation(this.layerProperties.getSecondaryAnnotationExpression()); break; } case LayerProperties.EXPRESSIONTYPE_PROPERTYNAME: { property = feature.getProperty(this.layerProperties.getSecondaryAnnotationExpression()); if (DEBUG) { if (logger.isDebugEnabled()) { logger.debug("evaluating PrimaryAnnotationExpression: setting SecondaryAnnotationExpression '" + property + "'"); } } if (property != null) { feature.setSecondaryAnnotation(property.toString()); } break; } case LayerProperties.EXPRESSIONTYPE_BEANSHELL: { feature.setSecondaryAnnotation(this.evaluateBeanShellExpression( feature, this.layerProperties.getSecondaryAnnotationExpression())); break; } case LayerProperties.EXPRESSIONTYPE_GROOVY: { feature.setSecondaryAnnotation(this.evaluateGroovyExpressions( feature, this.layerProperties.getSecondaryAnnotationExpression())); break; } } } /** * Evaluates a groovy expression. * * @param feature DOCUMENT ME! * @param expression DOCUMENT ME! * * @return DOCUMENT ME! */ protected String evaluateGroovyExpressions(final FT feature, String expression) { if (this.groovyShell == null) { if (DEBUG) { if (logger.isDebugEnabled()) { logger.debug("performing lazy first time initialisation of GroovyShell"); } } this.groovyShell = new GroovyShell(); } try { groovyShell.getContext().getVariables().clear(); /** Groovy keeps references to former scripts. This circumstance leads to * a steady growing of permGen space and in the worst case to * OutOfMemoryException. The method resetLoadedClasses() removes this * references. */ groovyShell.resetLoadedClasses(); for (final Object key : feature.getProperties().keySet()) { final Object property = feature.getProperty(key.toString()); groovyShell.setVariable(key.toString().replaceAll(":", "_"), property); } expression = expression.replaceAll(":", "_"); return groovyShell.evaluate(expression).toString(); } catch (Throwable t) { logger.error("could not evaluate groovy expression '" + expression + "'", t); return null; } } /** * DOCUMENT ME! * * @param feature DOCUMENT ME! * @param expression DOCUMENT ME! * * @return DOCUMENT ME! * * @throws UnsupportedOperationException DOCUMENT ME! */ protected String evaluateBeanShellExpression(final FT feature, final String expression) { throw new UnsupportedOperationException("BeanShell not supported"); } /** * Checks if the worker thread was cancelled and performs cleanup. * * @param workerThread DOCUMENT ME! * @param message DOCUMENT ME! * * @return true if the worker thread was cancelled */ protected boolean checkCancelled(final SwingWorker workerThread, final String message) { if ((workerThread != null) && workerThread.isCancelled()) { logger.warn("FRW[" + workerThread + "]: operation is canceled after " + message); this.lastCreatedfeatureVector.clear(); return true; } return false; } @Override public synchronized Vector<FT> getLastCreatedFeatures() { // return copy to prevent concurrent modification exception when thread is canceled return new Vector<FT>(this.lastCreatedfeatureVector); } /** * Determines if the service automatically generates unique IDs for all queryable features. Note that if this * operation returns {@code true}, the id expression is not evaluated. * * @return {@code true} if the service generates id} */ protected abstract boolean isGenerateIds(); // protected void processFeatures() throws Exception // { // if (AbstractFeatureService.this instanceof StaticFeatureService) // { // Coordinate[] polyCords = new Coordinate[5]; // polyCords[0] = new Coordinate(getBoundingBox().getX1(), getBoundingBox().getY1()); // polyCords[1] = new Coordinate(getBoundingBox().getX1(), getBoundingBox().getY2()); // polyCords[2] = new Coordinate(getBoundingBox().getX2(), getBoundingBox().getY2()); // polyCords[3] = new Coordinate(getBoundingBox().getX2(), getBoundingBox().getY1()); // polyCords[4] = new Coordinate(getBoundingBox().getX1(), getBoundingBox().getY1()); // Polygon boundingPolygon = (new GeometryFactory()).createPolygon((new GeometryFactory()).createLinearRing(polyCords), null); // // if (!(JTSAdapter.export(current.getDefaultGeometryPropertyValue())).intersects(boundingPolygon)) // { // //if(DEBUG)logger.debug("Feature ist nicht in boundingbox"); // continue; // } // } // // } /** * DOCUMENT ME! * * @param features DOCUMENT ME! * @param geom DOCUMENT ME! * @param query DOCUMENT ME! */ protected synchronized void updateLastCreatedFeatures(final Collection<FT> features, final Geometry geom, final QT query) { this.lastCreatedfeatureVector.clear(); this.lastCreatedfeatureVector.ensureCapacity(features.size()); this.lastCreatedfeatureVector.addAll(features); this.lastCreatedfeatureVector.trimToSize(); this.lastGeom = geom; this.lastQuery = query; } @Override public FeatureServiceFeature createNewFeature() { throw new UnsupportedOperationException(); } /** * DOCUMENT ME! * * @param geom DOCUMENT ME! * @param query DOCUMENT ME! * * @return DOCUMENT ME! */ protected boolean featuresAlreadyInMemory(final Geometry geom, final QT query) { if (((lastQuery == null) && (query != null)) || ((lastQuery != null) && (query != null) && !lastQuery.equals(query))) { return false; } else { return (lastGeom != null) && (geom.getSRID() == lastGeom.getSRID()) && geom.within(lastGeom); } } /** * DOCUMENT ME! * * @param query DOCUMENT ME! * @param geom DOCUMENT ME! * * @return DOCUMENT ME! * * @throws FeatureFactory.TooManyFeaturesException DOCUMENT ME! * @throws Exception DOCUMENT ME! */ protected Vector<FT> createFeaturesFromMemory(final QT query, final Geometry geom) throws FeatureFactory.TooManyFeaturesException, Exception { if (!featuresAlreadyInMemory(geom, query)) { return null; } final Vector<FT> featureList = new Vector<FT>(); for (final FT feature : lastCreatedfeatureVector) { if (geom.intersects(feature.getGeometry())) { featureList.add(feature); } } return featureList; } @Override public abstract AbstractFeatureFactory clone(); /** * DOCUMENT ME! * * @return DOCUMENT ME! */ protected List<Style> getStyle() { if (styles != null) { return styles.get("default"); } else { return null; } } /** * DOCUMENT ME! * * @param layerName DOCUMENT ME! * * @return DOCUMENT ME! */ public List<Style> getStyle(final String layerName) { if (layerName == null) { return getStyle(); } else if ((styles != null) && styles.containsKey(layerName)) { return styles.get(layerName); } else { return null; } } }