//$HeadURL$ /*---------------------------------------------------------------------------- This file is part of deegree, http://deegree.org/ Copyright (C) 2001-2009 by: Department of Geography, University of Bonn and lat/lon GmbH 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; either version 2.1 of the License, or (at your option) any later version. 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. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Contact information: lat/lon GmbH Aennchenstr. 19, 53177 Bonn Germany http://lat-lon.de/ Department of Geography, University of Bonn Prof. Dr. Klaus Greve Postfach 1147, 53001 Bonn Germany http://www.geographie.uni-bonn.de/deegree/ e-mail: info@deegree.org ----------------------------------------------------------------------------*/ package org.deegree.igeo.style; import static org.deegree.framework.util.DateUtil.formatISO8601Date; import java.math.BigInteger; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.deegree.datatypes.QualifiedName; import org.deegree.datatypes.Types; import org.deegree.framework.log.ILogger; import org.deegree.framework.log.LoggerFactory; import org.deegree.igeo.ChangeListener; import org.deegree.igeo.ValueChangedEvent; import org.deegree.igeo.dataadapter.DataAccessAdapter; import org.deegree.igeo.dataadapter.FeatureAdapter; import org.deegree.igeo.dataadapter.GridCoverageAdapter; import org.deegree.igeo.dataadapter.database.DatabaseFeatureAdapter; import org.deegree.igeo.mapmodel.Layer; import org.deegree.igeo.mapmodel.LayerChangedEvent; import org.deegree.igeo.mapmodel.LayerChangedEvent.LAYER_CHANGE_TYPE; import org.deegree.igeo.style.model.PropertyValue; import org.deegree.igeo.views.swing.style.StyleDialog.GEOMTYPE; import org.deegree.model.Identifier; import org.deegree.model.feature.Feature; import org.deegree.model.feature.FeatureCollection; import org.deegree.model.feature.FeatureProperty; import org.deegree.model.feature.schema.FeatureType; import org.deegree.model.feature.schema.PropertyType; import org.deegree.model.filterencoding.FilterEvaluationException; import org.deegree.model.spatialschema.Curve; import org.deegree.model.spatialschema.Envelope; import org.deegree.model.spatialschema.MultiCurve; import org.deegree.model.spatialschema.MultiPoint; import org.deegree.model.spatialschema.MultiSurface; import org.deegree.model.spatialschema.Surface; /** * <code>LayerCache</code> manages a list of layers. * * @author <a href="mailto:buesching@lat-lon.de">Lyn Buesching</a> * @author last edited by: $Author$ * * @version $Revision$, $Date$ */ public class LayerCache { private static final ILogger LOG = LoggerFactory.getLogger( LayerCache.class ); private static final String NO_DATA_STRING = "no data"; private static final int NO_DATA_INT = 0; private static final BigInteger NO_DATA_BIGINT = BigInteger.ZERO; private static final double NO_DATA_DOUBLE = Double.NaN; private static final Date NO_DATA_DATE = new Date(); private static LayerCache layerCache = new LayerCache(); private List<CachedLayer> layers = new ArrayList<CachedLayer>(); private LayerCache() { } /** * @return the only instance of a layer cache */ public static LayerCache getInstance() { return layerCache; } /** * @param layer * the layer to add to the cached layers, if not exits */ public void addLayer( Layer layer ) { if ( getCachedLayer( layer.getIdentifier() ) == null ) { layers.add( new CachedLayer( layer ) ); } } /** * Returns all properties loaded until now. * * @param layerId * the identifier of the layer * @return the properties of the layer with the given id loaded until now, or an empty list, if no layer with the * identifier exist * @throws */ public Map<QualifiedName, PropertyValue<?>> getProperties( Identifier layerId ) { CachedLayer layer = getCachedLayer( layerId ); if ( layer != null ) { return layer.getProperties(); } return new HashMap<QualifiedName, PropertyValue<?>>(); } /** * Loads all properties in the passed extent. * * @param layerId * the identifier of the layer * @param extent * the extent limiting the layers. * @return the properties of the layer with the given id in the extent, or an empty list, if no layer with the * identifier exist * @throws FilterEvaluationException */ public Map<QualifiedName, PropertyValue<?>> getProperties( Identifier layerId, Envelope extent ) { CachedLayer layer = getCachedLayer( layerId ); if ( layer != null ) { return layer.getProperties( extent ); } return new HashMap<QualifiedName, PropertyValue<?>>(); } /** * Loads all properties. * * @param layerId * the identifier of the layer * @return all properties of the layer with the given id, or an empty list, if no layer with the identifier exist */ public Map<QualifiedName, PropertyValue<?>> getAllProperties( Identifier layerId ) { CachedLayer layer = getCachedLayer( layerId ); if ( layer != null ) { return layer.getAllProperties(); } return new HashMap<QualifiedName, PropertyValue<?>>(); } /** * @param layerId * the identifier of the layer * @return the cached layer with the given id, or null if no layer with the identifier exist */ public CachedLayer getCachedLayer( Identifier layerId ) { for ( CachedLayer layer : layers ) { if ( layerId.equals( layer.layer.getIdentifier() ) ) { return layer; } } return null; } // //////////////////////////////////////////////////////////////////////////////////// // INNERCLASS // //////////////////////////////////////////////////////////////////////////////////// /** * <code>CachedLayer</code> caches a single org.deegree.igeo.mapmodel.Layer. All information required in the style * dialog will be cached in this class */ public class CachedLayer implements ChangeListener { private Layer layer; private boolean refreshRequested = true; private Map<QualifiedName, PropertyValue<?>> properties = new HashMap<QualifiedName, PropertyValue<?>>(); private Map<Envelope, Map<QualifiedName, PropertyValue<?>>> extentToProperties = new HashMap<Envelope, Map<QualifiedName, PropertyValue<?>>>(); private boolean refreshAllPropertiesRequested = true; private Map<QualifiedName, PropertyValue<?>> propertiesFullExtent = new HashMap<QualifiedName, PropertyValue<?>>(); private boolean isRaster = false; private boolean isOther = false; private Map<QualifiedName, FeatureType> featureTypes = new HashMap<QualifiedName, FeatureType>(); private Map<QualifiedName, GEOMTYPE> geometryProperties = new HashMap<QualifiedName, GEOMTYPE>(); public CachedLayer( Layer layer ) { this.layer = layer; layer.addChangeListener( this ); } /** * @return the properties */ public Map<QualifiedName, PropertyValue<?>> getProperties() { if ( refreshRequested ) load(); return properties; } /** * @return the properties */ public Map<QualifiedName, PropertyValue<?>> getProperties( Envelope extent ) { if ( !extentToProperties.containsKey( extent ) ) { load( extent ); } return extentToProperties.get( extent ); } /** * @return the properties */ public Map<QualifiedName, PropertyValue<?>> getAllProperties() { if ( refreshAllPropertiesRequested ) { loadFullExtent(); } return propertiesFullExtent; } /** * @return the isRaster */ public boolean isRaster() { if ( refreshRequested ) load(); return isRaster; } /** * @return the isOther */ public boolean isOther() { if ( refreshRequested ) load(); return isOther; } /** * @return the featureTypes */ public Set<QualifiedName> getFeatureTypes() { if ( refreshRequested ) load(); return featureTypes.keySet(); } /** * @return the featureTypes */ public FeatureType getFeatureType( QualifiedName qn ) { if ( refreshRequested ) load(); return featureTypes.get( qn ); } /** * @return the geometryProperties */ public Map<QualifiedName, GEOMTYPE> getGeometryProperties() { if ( refreshRequested ) load(); return geometryProperties; } /** * @return the max scale denominator of the layer */ public double getMaxScaleDenominator() { return layer.getMaxScaleDenominator(); } /** * @return the min scale denominator of the layer */ public double getMinScaleDenominator() { return layer.getMinScaleDenominator(); } /** * @param qn * the name of the geometry type * @return the type of the geometry identified by the given qualified name */ public GEOMTYPE getGeometryType( QualifiedName qn ) { GEOMTYPE gt = geometryProperties.get( qn ); if ( gt == null ) { geometryProperties.put( qn, GEOMTYPE.UNKNOWN ); for ( DataAccessAdapter adapter : layer.getDataAccess() ) { if ( adapter instanceof FeatureAdapter ) { Iterator<Feature> features = getFeatureCollection( adapter, null ).iterator(); if(features.hasNext()){ FeatureProperty[] fts = features.next().getProperties( qn ); if ( fts != null && fts.length > 0 ) { Object value = fts[0].getValue(); if ( value instanceof org.deegree.model.spatialschema.Point || value instanceof MultiPoint ) { geometryProperties.remove( qn ); geometryProperties.put( qn, GEOMTYPE.POINT ); } else if ( value instanceof Curve || value instanceof MultiCurve ) { geometryProperties.remove( qn ); geometryProperties.put( qn, GEOMTYPE.LINE ); } else if ( value instanceof Surface || value instanceof MultiSurface ) { geometryProperties.remove( qn ); geometryProperties.put( qn, GEOMTYPE.POLYGON ); } } } } } } return geometryProperties.get( qn ); } private void loadFullExtent() { for ( DataAccessAdapter adapter : layer.getDataAccess() ) { boolean loadFully = isFullLoadingSupported( adapter ); if ( loadFully ) { DatabaseFeatureAdapter fa = (DatabaseFeatureAdapter) adapter; FeatureType ft = ( (FeatureAdapter) adapter ).getSchema(); featureTypes.put( ft.getName(), ft ); PropertyType[] propertyTypes = ft.getProperties(); fa.getDistinctPropertyValues( propertiesFullExtent, geometryProperties, propertyTypes ); isOther = true; } else { propertiesFullExtent.putAll( properties ); } } refreshAllPropertiesRequested = false; } public boolean isFullLoadingSupported() { for ( DataAccessAdapter adapter : layer.getDataAccess() ) { if ( isFullLoadingSupported( adapter ) ) return true; } return false; } private boolean isFullLoadingSupported( DataAccessAdapter adapter ) { return adapter instanceof DatabaseFeatureAdapter && adapter.getDatasource().isLazyLoading(); } private void load() { load( null ); } /** * Reads all properties available for the selected layer. If a property is not from type 'geometry', the * features of the property will read and all values stored (not the doubles!). * * @param extent */ private void load( Envelope extent ) { resetProperties( extent ); for ( DataAccessAdapter adapter : layer.getDataAccess() ) { if ( adapter instanceof FeatureAdapter ) { adapter.refresh(); FeatureType ft = ( (FeatureAdapter) adapter ).getSchema(); featureTypes.put( ft.getName(), ft ); PropertyType[] propertyTypes = ft.getProperties(); for ( PropertyType type : propertyTypes ) { if ( type.getType() != Types.GEOMETRY ) { FeatureCollection fc = getFeatureCollection( adapter, extent ); int pt = type.getType(); switch ( pt ) { case Types.VARCHAR: PropertyValue<String> pv1 = new PropertyValue<String>( type ); for ( Iterator<Feature> iter = fc.iterator(); iter.hasNext(); ) { Feature element = iter.next(); // TODO FeatureProperty[] fps = element.getProperties( type.getName() ); String stringValue = NO_DATA_STRING; if ( fps != null && fps.length > 0 && fps[0].getValue() != null ) { try { if ( fps[0].getValue() instanceof Date ) { stringValue = formatISO8601Date( (Date) fps[0].getValue() ); } else { stringValue = (String) fps[0].getValue(); } } catch ( ClassCastException e ) { LOG.logError( "Could not cast value to string, where type is VARCHAR" ); } } pv1.putInMap( stringValue ); } insertInMap( type, pv1, extent ); break; case Types.BIGINT: PropertyValue<BigInteger> pv5 = new PropertyValue<BigInteger>( type ); for ( Iterator<Feature> iter = fc.iterator(); iter.hasNext(); ) { Feature element = iter.next(); // TODO FeatureProperty[] fps = element.getProperties( type.getName() ); BigInteger intValue = NO_DATA_BIGINT; if ( fps != null && fps.length > 0 && fps[0].getValue() != null ) { try { intValue = new BigInteger( fps[0].getValue().toString() ); } catch ( Exception e ) { LOG.logError( "Could not cast value to integer, where type is INTEGER", e ); } } pv5.putInMap( intValue ); } insertInMap( type, pv5, extent ); break; case Types.INTEGER: case Types.SMALLINT: PropertyValue<Integer> pv2 = new PropertyValue<Integer>( type ); for ( Iterator<Feature> iter = fc.iterator(); iter.hasNext(); ) { Feature element = iter.next(); // TODO FeatureProperty[] fps = element.getProperties( type.getName() ); int intValue = NO_DATA_INT; if ( fps != null && fps.length > 0 && fps[0].getValue() != null ) { try { intValue = Integer.parseInt( fps[0].getValue().toString() ); } catch ( Exception e ) { LOG.logError( "Could not cast value to integer, where type is INTEGER", e ); } } pv2.putInMap( intValue ); } insertInMap( type, pv2, extent ); break; case Types.DOUBLE: case Types.FLOAT: PropertyValue<Double> pv3 = new PropertyValue<Double>( type ); for ( Iterator<Feature> iter = fc.iterator(); iter.hasNext(); ) { Feature element = iter.next(); // TODO FeatureProperty[] fps = element.getProperties( type.getName() ); double doubleValue = NO_DATA_DOUBLE; if ( fps != null && fps.length > 0 ) { Object value = fps[0].getValue(); if ( value != null && value instanceof String && ( (String) value ).length() > 0 ) { try { doubleValue = Double.parseDouble( (String) value ); } catch ( Exception e ) { LOG.logError( "Could not cast value to double, where type is DOUBLE", e ); } } else if ( value instanceof Double ) { try { doubleValue = (Double) value; } catch ( Exception e ) { LOG.logError( "Could not cast value to double, where type is DOUBLE", e ); } } } pv3.putInMap( doubleValue ); } insertInMap( type, pv3, extent ); break; case Types.DATE: PropertyValue<Date> pv4 = new PropertyValue<Date>( type ); for ( Iterator<Feature> iter = fc.iterator(); iter.hasNext(); ) { Feature element = iter.next(); // TODO FeatureProperty[] fps = element.getProperties( type.getName() ); Date dateValue = NO_DATA_DATE; if ( fps != null && fps.length > 0 ) { Object value = fps[0].getValue(); if ( value != null && value instanceof String && ( (String) value ).length() > 0 ) { SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-mm-dd" ); try { dateValue = sdf.parse( (String) value ); } catch ( ParseException e ) { LOG.logError( "Could not cast value to date, where type is DATE", e ); } } else if ( value != null && value instanceof Date ) { dateValue = (Date) value; } } pv4.putInMap( dateValue ); } insertInMap( type, pv4, extent ); break; } } else { geometryProperties.put( type.getName(), null ); } } isOther = true; } else if ( adapter instanceof GridCoverageAdapter ) { isRaster = true; } } if ( extent == null ) { refreshRequested = false; } } private void insertInMap( PropertyType type, PropertyValue<?> pv, Envelope extent ) { if ( extent != null ) { extentToProperties.get( extent ).put( type.getName(), pv ); } else { properties.put( type.getName(), pv ); } } private void resetProperties( Envelope extent ) { if ( extent != null ) { if ( extentToProperties.containsKey( extent ) ) { extentToProperties.get( extent ).clear(); } else { extentToProperties.put( extent, new HashMap<QualifiedName, PropertyValue<?>>() ); } } else { properties.clear(); } } private FeatureCollection getFeatureCollection( DataAccessAdapter adapter, Envelope extent ) { if ( extent != null ) { try { return ( (FeatureAdapter) adapter ).getFeatureCollection( extent ); } catch ( FilterEvaluationException e ) { LOG.logWarning( "Could not get limited FeatureCollection to extent " + extent + ": " + e.getMessage() ); return ( (FeatureAdapter) adapter ).getFeatureCollection(); } } return ( (FeatureAdapter) adapter ).getFeatureCollection(); } @Override public void valueChanged( ValueChangedEvent event ) { // if the datasource changes, the properties have to be refreshed! if ( ( event instanceof LayerChangedEvent ) && ( ( (LayerChangedEvent) event ).getChangeType().equals( LAYER_CHANGE_TYPE.datasourceAdded ) || ( (LayerChangedEvent) event ).getChangeType().equals( LAYER_CHANGE_TYPE.datasourceRemoved ) || ( (LayerChangedEvent) event ).getChangeType().equals( LAYER_CHANGE_TYPE.datasourceChanged ) ) ) { refreshRequested = true; refreshAllPropertiesRequested = true; extentToProperties.clear(); } } } }