/* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved. * This code is licensed under the GPL 2.0 license, availible at the root * application directory. */ package org.vfny.geoserver.action.data; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.logging.Level; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.struts.action.ActionError; import org.apache.struts.action.ActionErrors; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import org.apache.struts.util.MessageResources; import org.vfny.geoserver.wms.responses.map.kml.KMLUtils; import org.vfny.geoserver.wms.responses.map.kml.RegionatingStrategy; import org.geotools.data.DataStore; import org.geotools.data.FeatureSource; import org.geotools.geometry.jts.JTS; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.referencing.CRS; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.referencing.FactoryException; import org.opengis.referencing.NoSuchAuthorityCodeException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.TransformException; import org.vfny.geoserver.action.ConfigAction; import org.vfny.geoserver.action.HTMLEncoder; import org.vfny.geoserver.config.AttributeTypeInfoConfig; import org.vfny.geoserver.config.ConfigRequests; import org.vfny.geoserver.config.DataConfig; import org.vfny.geoserver.config.DataStoreConfig; import org.vfny.geoserver.config.FeatureTypeConfig; import org.vfny.geoserver.form.data.AttributeForm; import org.vfny.geoserver.form.data.TypesEditorForm; import org.vfny.geoserver.global.FeatureTypeInfo; import org.vfny.geoserver.global.MetaDataLink; import org.vfny.geoserver.global.UserContainer; import org.vfny.geoserver.util.DataStoreUtils; import com.vividsolutions.jts.geom.Envelope; /** * These Action handles all the buttons for the SimpleFeatureType Editor. * * <p> * This one is more complicated then usual since not all the actions require * the form bean to be validated! I am going to have to hack a little bit to * make that happen, I may end up making the form bean validation differ * depending on the selected action. * </p> * * <p> * Buttons that make this action go: * * <ul> * <li> * Submit: update the FeatureTypeConfig held by the user, punt it back into * DataConfig and return to the FeatureTypeSelect screen. * </li> * <li> * Up and Down (for each attribute): not quite sure how to make these work yet * - I hope I dont have to give them different names. * </li> * </ul> * * As usual we will have to uninternationlize the action name provided to us. * </p> * * @author Richard Gould * @author Jody Garnett */ public class TypesEditorAction extends ConfigAction { public ActionForward execute(ActionMapping mapping, ActionForm form, UserContainer user, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { if (LOGGER.isLoggable(Level.FINER)) { LOGGER.finer(new StringBuffer("form bean:").append(form.getClass().getName()).toString()); } TypesEditorForm typeForm = (TypesEditorForm) form; String action = typeForm.getAction(); if (LOGGER.isLoggable(Level.FINER)) { LOGGER.finer(new StringBuffer("TypesEditorAction is ").append(action).toString()); } Locale locale = (Locale) request.getLocale(); MessageResources messages = getResources(request); final String SUBMIT = HTMLEncoder.decode(messages.getMessage(locale, "label.submit")); final String ADD = HTMLEncoder.decode(messages.getMessage(locale, "label.add")); final String BBOX = HTMLEncoder.decode(messages.getMessage(locale, "config.data.calculateBoundingBox.label")); final String LOOKUP_SRS = HTMLEncoder.decode(messages.getMessage(locale, "config.data.lookupSRS.label")); if (LOGGER.isLoggable(Level.FINER)) { LOGGER.finer(new StringBuffer("BBOX: ").append(BBOX).toString()); } final String NEWSLD = HTMLEncoder.decode(messages.getMessage(locale, "config.data.sldWizard.label")); if (typeForm.getAutoGenerateExtent().equals("true")) { if ((typeForm.getSRS() == null) || typeForm.getSRS().trim().equals("0")) { executeLookupSRS(mapping, typeForm, user, request); } executeBBox(mapping, typeForm, user, request); return executeSubmit(mapping, typeForm, user, request); } if (SUBMIT.equals(action)) { return executeSubmit(mapping, typeForm, user, request); } if (action.equals(BBOX)) { return executeBBox(mapping, typeForm, user, request); } if (action.equals(LOOKUP_SRS)) { return executeLookupSRS(mapping, typeForm, user, request); } if (action.equals(NEWSLD)) { // if the SLDWizard button was hit return mapping.findForward("SLDWizard"); } List attributes = typeForm.getAttributes(); if (action.startsWith("up_")) { int index = Integer.parseInt(action.substring(3)); Object attribute = attributes.remove(index); attributes.add(index - 1, attribute); } else if (action.startsWith("down_")) { int index = Integer.parseInt(action.substring(5)); Object attribute = attributes.remove(index); attributes.add(index + 1, attribute); } else if (action.startsWith("delete_")) { int index = Integer.parseInt(action.substring(7)); attributes.remove(index); } else if (action.equals(ADD)) { executeAdd(mapping, typeForm, user, request); } // Update, Up, Down, Add, Remove need to resync sync(typeForm, user.getFeatureTypeConfig(), request); form.reset(mapping, request); return mapping.findForward("config.data.type.editor"); } private ActionForward executeLookupSRS(ActionMapping mapping, TypesEditorForm typeForm, UserContainer user, HttpServletRequest request) throws IOException, ServletException { DataConfig dataConfig = getDataConfig(); DataStoreConfig dsConfig = dataConfig.getDataStore(typeForm.getDataStoreId()); DataStore dataStore = null; try { dataStore = dsConfig.findDataStore(request.getSession().getServletContext()); SimpleFeatureType featureType = dataStore.getSchema(typeForm.getTypeName()); FeatureSource<SimpleFeatureType, SimpleFeature> fs; fs = dataStore.getFeatureSource(featureType.getTypeName()); CoordinateReferenceSystem crs = fs.getSchema().getCoordinateReferenceSystem(); String s = CRS.lookupIdentifier(crs, true); if (s == null) { typeForm.setSRS("UNKNOWN"); } else if (s.indexOf(':') != -1) { typeForm.setSRS(s.substring(s.indexOf(':') + 1)); } else { typeForm.setSRS(s); } } catch (Exception e) { LOGGER.log(Level.FINE, "Error occurred trying to lookup the SRS", e); typeForm.setSRS("UNKNOWN"); } finally { if(dataStore != null) dataStore.dispose(); } return mapping.findForward("config.data.type.editor"); } /** * Populate the bounding box fields from the source and pass control back * to the UI * * @param mapping DOCUMENT ME! * @param typeForm DOCUMENT ME! * @param user DOCUMENT ME! * @param request DOCUMENT ME! * * @return DOCUMENT ME! * * @throws IOException DOCUMENT ME! * @throws ServletException DOCUMENT ME! */ private ActionForward executeBBox(ActionMapping mapping, TypesEditorForm typeForm, UserContainer user, HttpServletRequest request) throws IOException, ServletException { DataConfig dataConfig = getDataConfig(); DataStoreConfig dsConfig = dataConfig.getDataStore(typeForm.getDataStoreId()); DataStore dataStore = null; try { dataStore = dsConfig.findDataStore(request.getSession().getServletContext()); SimpleFeatureType featureType = dataStore.getSchema(typeForm.getTypeName()); FeatureSource<SimpleFeatureType, SimpleFeature> fs; fs = dataStore.getFeatureSource(featureType.getTypeName()); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine(new StringBuffer("calculating bbox for their dataset").toString()); } Envelope envelope = DataStoreUtils.getBoundingBoxEnvelope(fs); if (envelope.isNull()) // there's no data in the featuretype!! { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine(new StringBuffer("SimpleFeatureType '").append(featureType.getTypeName()) .append("' has a null bounding box") .toString()); } ActionErrors errors = new ActionErrors(); errors.add(ActionErrors.GLOBAL_ERROR, new ActionError("error.data.nullBBOX", featureType.getTypeName())); saveErrors(request, errors); return mapping.findForward("config.data.type.editor"); } // do a translation from the data's coordinate system to lat/long //TODO: DJB: NOTE: 1/2 of the config stuff has the srs as an int, 1/2 as string!! We should be more consistent! String srs = typeForm.getSRS(); // what the user typed in for the srs in the form if (srs.indexOf(':') == -1) { // check to see if its of the form "EPSG:#" (or some such thing) srs = "EPSG:" + srs; //assume they wanted to use an EPSG number } CoordinateReferenceSystem crsDeclared = CRS.decode(srs); CoordinateReferenceSystem original = null; if (featureType.getGeometryDescriptor() != null) { original = featureType.getCoordinateReferenceSystem(); } if (original == null) { original = crsDeclared; } CoordinateReferenceSystem crsLatLong = CRS.decode("EPSG:4326"); // latlong // let's show coordinates in the declared crs, not in the native one, to // avoid confusion (since on screen we do have the declared one, the native is // not visible) Envelope declaredEnvelope = envelope; if (!CRS.equalsIgnoreMetadata(original, crsDeclared)) { if(typeForm.getSrsHandlingCode() == FeatureTypeInfo.REPROJECT) { MathTransform xform = CRS.findMathTransform(original, crsDeclared, true); declaredEnvelope = JTS.transform(envelope, null, xform, 10); //convert data bbox to lat/long } else if(typeForm.getSrsHandlingCode() == FeatureTypeInfo.FORCE) { declaredEnvelope = new ReferencedEnvelope(envelope, crsDeclared); } } LOGGER.finer("Seeting form's data envelope: " + declaredEnvelope); typeForm.setDataMinX(Double.toString(declaredEnvelope.getMinX())); typeForm.setDataMaxX(Double.toString(declaredEnvelope.getMaxX())); typeForm.setDataMinY(Double.toString(declaredEnvelope.getMinY())); typeForm.setDataMaxY(Double.toString(declaredEnvelope.getMaxY())); // preserve the actual native envelope as well typeForm.setNativeMinX(Double.toString(envelope.getMinX())); typeForm.setNativeMaxX(Double.toString(envelope.getMaxX())); typeForm.setNativeMinY(Double.toString(envelope.getMinY())); typeForm.setNativeMaxY(Double.toString(envelope.getMaxY())); MathTransform xform = CRS.findMathTransform(original, crsLatLong, true); Envelope xformed_envelope = JTS.transform(envelope, xform); //convert data bbox to lat/long typeForm.setMinX(Double.toString(xformed_envelope.getMinX())); typeForm.setMaxX(Double.toString(xformed_envelope.getMaxX())); typeForm.setMinY(Double.toString(xformed_envelope.getMinY())); typeForm.setMaxY(Double.toString(xformed_envelope.getMaxY())); } catch (NoSuchAuthorityCodeException e) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine(e.getLocalizedMessage()); LOGGER.fine(e.getStackTrace().toString()); } ActionErrors errors = new ActionErrors(); errors.add(ActionErrors.GLOBAL_ERROR, new ActionError("error.data.couldNotFindSRSAuthority", e.getLocalizedMessage(), e.getAuthorityCode())); saveErrors(request, errors); return mapping.findForward("config.data.type.editor"); } catch (FactoryException fe) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine(fe.getLocalizedMessage()); LOGGER.fine(fe.getStackTrace().toString()); } ActionErrors errors = new ActionErrors(); errors.add(ActionErrors.GLOBAL_ERROR, new ActionError("error.data.factoryException", fe.getLocalizedMessage())); saveErrors(request, errors); return mapping.findForward("config.data.type.editor"); } catch (TransformException te) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine(te.getLocalizedMessage()); LOGGER.fine(te.getStackTrace().toString()); } ActionErrors errors = new ActionErrors(); errors.add(ActionErrors.GLOBAL_ERROR, new ActionError("error.data.transformException")); saveErrors(request, errors); return mapping.findForward("config.data.type.editor"); } finally { if(dataStore != null) dataStore.dispose(); } return mapping.findForward("config.data.type.editor"); } /** * Sync generated attributes with schemaBase. * * @param form * @param config */ private void sync(TypesEditorForm form, FeatureTypeConfig config, HttpServletRequest request) { config.setName(form.getTypeName()); if(form.getAlias() != null && "".equals(form.getAlias().trim())) config.setAlias(null); else config.setAlias(form.getAlias()); config.setAbstract(form.getAbstract()); config.setDefaultStyle(form.getStyleId()); config.getStyles().clear(); if (form.getOtherSelectedStyles() != null) { for (int i = 0; i < form.getOtherSelectedStyles().length; i++) { config.addStyle(form.getOtherSelectedStyles()[i]); } } config.setSRS(Integer.parseInt(form.getSRS())); config.setTitle(form.getTitle()); Envelope latLonBbox = getBoundingBox(form); config.setLatLongBBox(latLonBbox); config.setNativeBBox(getNativeBBox(form)); config.setKeywords(keyWords(form)); config.setMetadataLinks(metadataLinks(form)); config.setWmsPath(form.getWmsPath()); config.setCacheMaxAge(form.getCacheMaxAge()); config.setCachingEnabled(form.isCachingEnabled()); config.setIndexingEnabled(form.isIndexingEnabled()); config.setMaxFeatures(Integer.parseInt(form.getMaxFeatures())); // Regionating stuff. We need to check for changes and clear the regionating cache if // settings changed. boolean regionatorNeedsCleaning = false; Integer limit = null; regionatorNeedsCleaning |= config.getRegionateAttribute() != null && !config.getRegionateAttribute().equals(form.getRegionateAttribute()); regionatorNeedsCleaning |= config.getRegionateStrategy() != null && !config.getRegionateStrategy().equals(form.getRegionateStrategy()); try { limit = Integer.valueOf(form.getRegionateFeatureLimit()); regionatorNeedsCleaning |= limit.intValue() != (config.getRegionateFeatureLimit()); } catch (NumberFormatException nfe){ // leave the previous value } if (regionatorNeedsCleaning) { String dsName = config.getDataStoreId(); if (dsName != null && getData().getDataStoreInfo(dsName) != null){ String nsPrefix = getData().getDataStoreInfo(dsName).getNamesSpacePrefix(); String qualifiedname = getData().getDataStoreInfo(config.getDataStoreId()).getNamesSpacePrefix() + ":" + config.getName(); try { FeatureTypeInfo fti = getData().getFeatureTypeInfo(qualifiedname); RegionatingStrategy rs = KMLUtils.findStrategyByName(config.getRegionateStrategy()); if (rs != null) rs.clearCache((org.geoserver.catalog.FeatureTypeInfo)fti.getLayerInfo().getResource()); } catch (NoSuchElementException e){ LOGGER.log( Level.FINE, "Changed regionating settings on new featuretype, no cleanup needed." ); } } } if (!(null == form.getNameTemplate() || "null".equals(form.getNameTemplate()))){ config.setNameTemplate(form.getNameTemplate()); } config.setRegionateAttribute(form.getRegionateAttribute()); config.setRegionateStrategy(form.getRegionateStrategy()); if (limit != null) config.setRegionateFeatureLimit(limit); config.setSRSHandling(form.getSrsHandlingCode()); if (!form.isCachingEnabledChecked()) { config.setCachingEnabled(false); } if (!form.isIndexingEnabledChecked()){ config.setIndexingEnabled(false); } String schemaBase = form.getSchemaBase(); if ((schemaBase == null) || schemaBase.equals("") || schemaBase.equals("--")) { config.setSchemaBase(null); config.setSchemaName(null); config.setSchemaAttributes(null); } else { config.setSchemaBase(schemaBase); String schemaName = config.getSchemaName(); List schemaAttributes = config.getSchemaAttributes(); System.out.println("in non null sb, sname: " + schemaName + ", satts: " + schemaAttributes); if ((schemaName == null) || (schemaName.trim().length() == 0)) { schemaName = form.getTypeName() + "_Type"; //HACK: For some reason only when editing an already exisitng //featureType, on the first time of switching to the editor //it gets a full schemaAttribute list, and I can't find where //so for now we are just relying on schemaName being null or schemaAttributes = null; //System.out.println("testing on schemaAtts: " + schemaAttributes); config.setSchemaName(schemaName); } else { config.setSchemaName(form.getSchemaName()); } if ((schemaAttributes == null) || schemaAttributes.isEmpty()) { schemaAttributes = new ArrayList(); List createList = form.getCreateableAttributes(); System.out.println("schemaAtts null, createList: " + createList); SimpleFeatureType fType = getFeatureType(form, request); for (int i = 0; i < fType.getAttributeCount(); i++) { AttributeDescriptor attType = fType.getDescriptor(i); AttributeTypeInfoConfig attributeConfig = new AttributeTypeInfoConfig(attType); schemaAttributes.add(attributeConfig); //new ArrayList(); //DataStoreConfig dsConfig = config. //SimpleFeatureType featureType = config.get } config.setSchemaAttributes(schemaAttributes); } else { config.setSchemaAttributes(form.toSchemaAttributes()); } } // config.setSchemaAttributes(form.toSchemaAttributes()); LOGGER.fine("config schema atts is " + config.getSchemaAttributes()); //config.setSchemaAttributes(form.toSchemaAttributes()); } /** * Convert a boudning box in latintute/longitude coordinates to another CRS, specified by name. * @param latLonBbox the latitude/longitude boudning box * @param crsName the name of the CRS to which it should be converted * @return the converted bounding box * @throws Exception if anything goes wrong */ private Envelope convertBBoxFromLatLon(Envelope latLonBbox, String crsName) throws Exception { CoordinateReferenceSystem latLon = CRS.decode("EPSG:4326"); CoordinateReferenceSystem nativeCRS = CRS.decode(crsName); Envelope env = null; if (!CRS.equalsIgnoreMetadata(latLon, nativeCRS)){ MathTransform xform = CRS.findMathTransform(latLon, nativeCRS, true); env = JTS.transform(latLonBbox, null, xform, 10); // convert databbox to native CRS } else { env = latLonBbox; } return env; } private void executeAdd(ActionMapping mapping, TypesEditorForm form, UserContainer user, HttpServletRequest request) { String attributeName = form.getNewAttribute(); SimpleFeatureType fType = getFeatureType(form, request); AttributeForm newAttribute = newAttributeForm(attributeName, fType); form.getAttributes().add(newAttribute); } private AttributeForm newAttributeForm(String attributeName, SimpleFeatureType featureType) { AttributeDescriptor attributeType = featureType.getDescriptor(attributeName); AttributeTypeInfoConfig attributeConfig = new AttributeTypeInfoConfig(attributeType); AttributeForm newAttribute = new AttributeForm(attributeConfig, attributeType); return newAttribute; } private SimpleFeatureType getFeatureType(TypesEditorForm form, HttpServletRequest request) { SimpleFeatureType featureType = null; DataStore dataStore = null; try { DataConfig config = ConfigRequests.getDataConfig(request); DataStoreConfig dataStoreConfig = config.getDataStore(form.getDataStoreId()); dataStore = dataStoreConfig.findDataStore(getServlet().getServletContext()); featureType = dataStore.getSchema(form.getTypeName()); } catch (IOException e) { // DataStore unavailable! } finally { if(dataStore != null) dataStore.dispose(); } return featureType; } /** * Execute Submit Action. * * @param mapping * @param form * @param user * @param request * * @return */ private ActionForward executeSubmit(ActionMapping mapping, TypesEditorForm form, UserContainer user, HttpServletRequest request) { FeatureTypeConfig config = user.getFeatureTypeConfig(); // clean up old names (but make sure not to delete another config which happens // to use the same name DataConfig dataConfig = (DataConfig) getDataConfig(); String keyName = config.getDataStoreId() + ":" + config.getName(); String keyAlias = config.getDataStoreId() + ":" + config.getAlias(); FeatureTypeConfig oldConfig = dataConfig.getFeatureTypeConfig(keyName); if(oldConfig != null && oldConfig == config) dataConfig.removeFeatureType(keyName); if(config.getAlias() != null) { oldConfig = dataConfig.getFeatureTypeConfig(keyAlias); if(oldConfig != null && oldConfig == config) dataConfig.removeFeatureType(keyAlias); } sync(form, config, request); // recompute after synch keyName = config.getDataStoreId() + ":" + config.getName(); keyAlias = config.getDataStoreId() + ":" + config.getAlias(); if(config.getAlias() != null && !"".equals(config.getAlias())) dataConfig.addFeatureType(keyAlias, config); else dataConfig.addFeatureType(keyName, config); // Don't think reset is needed (as me have moved on to new page) // form.reset(mapping, request); getApplicationState().notifyConfigChanged(); // Feature no longer selected user.setFeatureTypeConfig(null); return mapping.findForward("config.data.type"); } /** * DOCUMENT ME! * * @param typeForm * * @return Bounding box in lat long */ private Envelope getBoundingBox(TypesEditorForm typeForm) { return new Envelope(Double.parseDouble(typeForm.getMinX()), Double.parseDouble(typeForm.getMaxX()), Double.parseDouble(typeForm.getMinY()), Double.parseDouble(typeForm.getMaxY())); } /** * DOCUMENT ME! * * @param typeForm * * @return Bounding box in lat long */ private Envelope getNativeBBox(TypesEditorForm typeForm) { // here, we try to use the native bbox computed during "generate", but if the // user specified the bbox by hand, we have to resort to back-project the lat/lon one try { return new Envelope(Double.parseDouble(typeForm.getNativeMinX()), Double.parseDouble(typeForm.getNativeMaxX()), Double.parseDouble(typeForm.getNativeMinY()), Double.parseDouble(typeForm.getNativeMaxY())); } catch(NumberFormatException e) { return null; } } /** * DOCUMENT ME! * * @param typeForm * * @return Set of keywords */ private Set keyWords(TypesEditorForm typeForm) { HashSet keywords = new HashSet(); String[] array = (typeForm.getKeywords() != null) ? typeForm.getKeywords().split(",") : new String[0]; for (int i = 0; i < array.length; i++) { keywords.add(array[i].trim()); } return keywords; } private Set metadataLinks(TypesEditorForm typeForm) { HashSet links = new HashSet(); MetaDataLink link = getLink(typeForm, 0); if (link != null) { links.add(link); } link = getLink(typeForm, 1); if (link != null) { links.add(link); } return links; } private MetaDataLink getLink(TypesEditorForm typeForm, int index) { MetaDataLink link = typeForm.getMetadataLink(index); if ((link.getContent() == null) || link.getContent().trim().equals("")) { return null; } return link; } DataStore aquireDataStore(String dataStoreID) throws IOException { DataConfig dataConfig = getDataConfig(); DataStoreConfig dataStoreConfig = dataConfig.getDataStore(dataStoreID); Map params = dataStoreConfig.getConnectionParams(); return DataStoreUtils.getDataStore(params); } SimpleFeatureType getSchema(String dataStoreID, String typeName) throws IOException { DataStore dataStore = null; try { dataStore = aquireDataStore(dataStoreID); return dataStore.getSchema(typeName); } finally { if(dataStore != null) dataStore.dispose(); } } }