/* (c) 2014 - 2015 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.wfs;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.FeatureTypeInfo;
import org.geoserver.catalog.ResourceInfo;
import org.geoserver.config.GeoServer;
import org.geoserver.config.SettingsInfo;
import org.geoserver.platform.Operation;
import org.geoserver.platform.ServiceException;
import org.geoserver.wfs.request.FeatureCollectionResponse;
import org.geoserver.wfs.request.GetFeatureRequest;
import org.geoserver.wfs.response.WFSResponse;
import org.geotools.feature.FeatureCollection;
import org.opengis.feature.type.FeatureType;
/**
* Base class for a response to a WFS GetFeature operation.
* <p>
* The result of a GetFeature operation is an instance of
* {@link FeatureCollectionResponse}. Subclasses are responsible for serializing
* an instance of this type in {@link #write(FeatureCollectionResponse, OutputStream, Operation)}.
* </p>
* <p>
* Subclasses also need declare the mime-type in which the format is encoded.
* </p>
*
* @author Gabriel Rold?n, Axios Engineering
* @author Justin Deoliveira, The Open Planning Project, jdeolive@openplans.org
*
*/
public abstract class WFSGetFeatureOutputFormat extends WFSResponse {
/**
* Based on definition of valid xml element name at http://www.w3.org/TR/xml/#NT-Name
*/
static final Pattern XML_ELEMENT = Pattern.compile("[:A-Z_a-z\\u00C0\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02ff\\u0370-\\u037d"
+ "\\u037f-\\u1fff\\u200c\\u200d\\u2070-\\u218f\\u2c00-\\u2fef\\u3001-\\ud7ff"
+ "\\uf900-\\ufdcf\\ufdf0-\\ufffd\\x10000-\\xEFFFF]"
+ "[:A-Z_a-z\\u00C0\\u00D6\\u00D8-\\u00F6"
+ "\\u00F8-\\u02ff\\u0370-\\u037d\\u037f-\\u1fff\\u200c\\u200d\\u2070-\\u218f"
+ "\\u2c00-\\u2fef\\u3001-\\udfff\\uf900-\\ufdcf\\ufdf0-\\ufffd\\\\x10000-\\\\xEFFFF\\-\\.0-9"
+ "\\u00b7\\u0300-\\u036f\\u203f-\\u2040]*\\Z");
/**
* logger
*/
protected static Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geoserver.wfs");
/**
* Constructor which sets the outputFormat.
*
* @param outputFormat The well-known name of the format, not <code>null</code>
*/
public WFSGetFeatureOutputFormat(GeoServer gs, String outputFormat) {
super(gs, FeatureCollectionResponse.class, outputFormat);
}
/**
* Constructor which sets the outputFormats.
*
* @param outputFormats Set of well-known name of the format, not <code>null</code>
*/
public WFSGetFeatureOutputFormat(GeoServer gs, Set<String> outputFormats) {
super(gs, FeatureCollectionResponse.class, outputFormats);
}
/**
* Returns the mime type <code>text/xml</code>.
* <p>
* Subclasses should override this method to provide a diffent output
* format.
* </p>
*/
public String getMimeType(Object value, Operation operation)
throws ServiceException {
return "text/xml";
}
/**
* Ensures that the operation being executed is a GetFeature operation.
* <p>
* Subclasses may implement
* </p>
*/
public boolean canHandle(Operation operation) {
//GetFeature operation?
if ("GetFeature".equalsIgnoreCase(operation.getId())
|| "GetFeatureWithLock".equalsIgnoreCase(operation.getId())) {
//also check that the resultType is "results"
GetFeatureRequest req = GetFeatureRequest.adapt(operation.getParameters()[0]);
if (req.isResultTypeResults()) {
//call subclass hook
return canHandleInternal(operation);
}
}
return false;
}
/**
* Capabilities output format string. Something that's a valid XML element name.
* This should be overriden in each outputformat subclass, and if it's not a warning will be
* issued.
*/
public String getCapabilitiesElementName() {
String of = getOutputFormat();
if(of == null) {
return null;
}
// wfs 1.1 form is not a valid xml element, do a check
if (XML_ELEMENT.matcher(of).matches()) {
return of;
} else {
LOGGER.severe("ERROR IN " + this.getClass() + " IMPLEMENTATION. getCapabilitiesElementName() should return a" +
"valid XML element name string for use in the WFS 1.0.0 capabilities document.");
String name = this.getClass().getName();
if ( name.indexOf('.') != -1 ) {
name = name.substring(name.lastIndexOf('.') + 1);
}
return name;
}
}
/**
* Returns the list of output format names generated by this format, for inclusion in
* the WFS 1.0 capabilities document as XML element names
*
*/
public List<String> getCapabilitiesElementNames() {
String name = getCapabilitiesElementName();
if(name == null) {
return Collections.emptyList();
} else {
return Arrays.asList(name);
}
}
/**
* Subclasses can delegate to this method if they want the full list of valid
* output format element names to be returned in the WFS 1.0 capabilities
*
*/
protected List<String> getAllCapabilitiesElementNames() {
List<String> result = new ArrayList<String>();
for (String name : getOutputFormats()) {
if (XML_ELEMENT.matcher(name).matches()) {
result.add(name);
}
}
// have the output order be independent of the used JDK
Collections.sort(result);
return result;
}
/**
* Hook for subclasses to add addtional checks to {@link #canHandle(Operation)}.
* <p>
* Subclasses may override this method if need be, the default impelementation
* returns <code>true</code>
* </p>
* @param operation The operation being performed.
*
* @return <code>true</code> if the output format can handle the operation,
* otherwise <code>false</code>
*/
protected boolean canHandleInternal(Operation operation) {
return true;
}
/**
* Calls through to {@link #write(FeatureCollectionResponse, OutputStream, Operation)}.
*/
public final void write(Object value, OutputStream output, Operation operation)
throws IOException, ServiceException {
//for WFS 2.0 we changed the input object type to be the request object adapter, but there
// is other code (like WMS GetFeatureInfo) that passes in the old objects, so do a check
if (value instanceof FeatureCollectionResponse) {
write((FeatureCollectionResponse) value, output, operation);
}
else {
write(FeatureCollectionResponse.adapt(value), output, operation);
}
}
protected int getNumDecimals(List featureCollections, GeoServer geoServer, Catalog catalog) {
int numDecimals = -1;
for (int i = 0; i < featureCollections.size(); i++) {
FeatureCollection features = (FeatureCollection) featureCollections.get(i);
FeatureType featureType = features.getSchema();
ResourceInfo meta = catalog
.getResourceByName(featureType.getName(), ResourceInfo.class);
// track num decimals, in cases where the query has multiple types we choose the max
// of all the values (same deal as above, might not be a vector due to GetFeatureInfo
// reusing this)
if (meta instanceof FeatureTypeInfo) {
int ftiDecimals = ((FeatureTypeInfo) meta).getNumDecimals();
if (ftiDecimals > 0) {
numDecimals = numDecimals == -1 ? ftiDecimals : Math.max(numDecimals,
ftiDecimals);
}
}
}
SettingsInfo settings = geoServer.getSettings();
if (numDecimals == -1) {
numDecimals = settings.getNumDecimals();
}
return numDecimals;
}
/**
* Serializes the feature collection in the format declared.
*
* @param featureCollection The feature collection.
* @param output The output stream to serialize to.
* @param getFeature The GetFeature operation descriptor.
*/
protected abstract void write(FeatureCollectionResponse featureCollection, OutputStream output,
Operation getFeature) throws IOException, ServiceException;
}