org.opengis.cite.iso19142.util.WFSClient Maven / Gradle / Ivy
package org.opengis.cite.iso19142.util;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.UriBuilder;
import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;
import org.opengis.cite.iso19142.Namespaces;
import org.opengis.cite.iso19142.ProtocolBinding;
import org.opengis.cite.iso19142.WFS2;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.client.filter.LoggingFilter;
import com.sun.jersey.core.util.MultivaluedMapImpl;
/**
* A WFS 2.0 client component.
*/
public class WFSClient {
private static final Logger LOGR = Logger.getLogger(WFSClient.class
.getPackage().getName());
protected Client client;
/** A Document that describes the service under test. */
protected Document wfsMetadata;
/** The set of message bindings broadly implemented by the SUT. */
protected Set globalBindings;
/** The list of feature types recognized by the SUT. */
protected List featureTypes;
/**
* Default client constructor. The client is configured to consume SOAP
* message entities. The request and response may be logged to a default JDK
* logger (in the namespace "com.sun.jersey.api.client").
*/
public WFSClient() {
ClientConfig config = new DefaultClientConfig();
config.getClasses().add(SOAPMessageConsumer.class);
this.client = Client.create(config);
this.client.addFilter(new LoggingFilter());
}
/**
* Constructs a client that is aware of the capabilities of some WFS.
*
* @param wfsMetadata
* A service description (e.g. WFS capabilities document).
*/
public WFSClient(Document wfsMetadata) {
this();
this.wfsMetadata = wfsMetadata;
this.featureTypes = ServiceMetadataUtils.getFeatureTypes(wfsMetadata);
this.globalBindings = ServiceMetadataUtils
.getGlobalBindings(wfsMetadata);
}
public Document getServiceDescription() {
return wfsMetadata;
}
/**
* Sets the service description obtained using the given InputStream. The
* standard description is an XML representation of a WFS capabilities
* document.
*
* @param srvMetadata
* An InputStream supplying the service metadata.
* @throws SAXException
* If any I/O errors occur.
* @throws IOException
* If any parsing errors occur.
*/
public void setServiceDescription(InputStream srvMetadata)
throws SAXException, IOException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
try {
DocumentBuilder docBuilder = factory.newDocumentBuilder();
Document doc = docBuilder.parse(srvMetadata);
if (doc.getDocumentElement().getLocalName()
.equals(WFS2.WFS_CAPABILITIES))
this.wfsMetadata = docBuilder.parse(srvMetadata);
} catch (ParserConfigurationException e) {
LOGR.log(Level.WARNING, e.getMessage());
}
}
/**
* Invokes a stored query using any supported protocol binding (request
* encoding).
*
* @param queryId
* The stored query identifier.
* @param params
* A collection of query parameters distinguished by name (may be
* empty, e.g. {@literal Collections..emptyMap()}
* ).
* @return A Document representing the XML response entity, or {@code null}
* if the response doesn't contain one.
*/
public Document invokeStoredQuery(String queryId, Map params) {
Document req = WFSRequest.createRequestEntity("GetFeature");
WFSRequest.appendStoredQuery(req, queryId, params);
ProtocolBinding binding = globalBindings.iterator().next();
return retrieveXMLResponseEntity(req, binding);
}
/**
* Retrieves feature representations by type name.
*
* @param typeName
* A QName denoting the feature type.
* @param count
* The maximum number of features to fetch (> 0). If count
* < 1, the default value (10) applies.
* @param binding
* The ProtocolBinding to use for this request; if {@code null} a
* global binding will be used.
* @return A Document representing the XML response entity, or {@code null}
* if the response doesn't contain one.
*/
public Document getFeatureByType(QName typeName, int count,
ProtocolBinding binding) {
if (null == binding) {
binding = globalBindings.iterator().next();
}
Document req = WFSRequest.createRequestEntity("GetFeature");
if (count > 0) {
req.getDocumentElement().setAttribute("count",
Integer.toString(count));
}
WFSRequest.appendSimpleQuery(req, typeName);
return retrieveXMLResponseEntity(req, binding);
}
/**
* Submits a request to delete a collection of features specified by
* identifier and type name.
*
* @param features
* A Map containing entries that specify a feature by identifier
* (gml:id attribute value) and type name (QName).
* @param binding
* The ProtocolBinding to use.
* @return A Document representing the XML response entity, or {@code null}
* if the response doesn't contain one.
*/
public Document delete(Map features, ProtocolBinding binding) {
Document req = WFSRequest.createRequestEntity(WFS2.TRANSACTION);
for (Map.Entry entry : features.entrySet()) {
QName typeName = entry.getValue();
Element delete = req.createElementNS(Namespaces.WFS, "Delete");
delete.setPrefix("wfs");
delete.setAttribute(XMLConstants.XMLNS_ATTRIBUTE + ":tns",
typeName.getNamespaceURI());
delete.setAttribute("typeName", "tns:" + typeName.getLocalPart());
req.getDocumentElement().appendChild(delete);
Element filter = req.createElementNS(Namespaces.FES, "Filter");
delete.appendChild(filter);
Element resourceId = req.createElementNS(Namespaces.FES,
"ResourceId");
resourceId.setAttribute("rid", entry.getKey());
filter.appendChild(resourceId);
}
if (TestSuiteLogger.isLoggable(Level.FINE)) {
TestSuiteLogger.log(Level.FINE, XMLUtils.writeNodeToString(req));
}
return executeTransaction(req, binding);
}
/**
* Submits a request to insert a collection of GML feature instances.
*
* @param features
* A {@literal List} containing one or more feature
* representations.
* @param binding
* The ProtocolBinding to use.
* @return A Document representing the XML response entity, or {@code null}
* if the response doesn't contain one.
*/
public Document insert(List features, ProtocolBinding binding) {
if (features.isEmpty()) {
throw new IllegalArgumentException(
"No features instances to insert.");
}
Document req = WFSRequest.createRequestEntity(WFS2.TRANSACTION);
Element insert = req.createElementNS(Namespaces.WFS, "Insert");
insert.setPrefix("wfs");
req.getDocumentElement().appendChild(insert);
for (Element feature : features) {
insert.appendChild(req.importNode(feature, true));
}
if (TestSuiteLogger.isLoggable(Level.FINE)) {
TestSuiteLogger.log(Level.FINE, XMLUtils.writeNodeToString(req));
}
return executeTransaction(req, binding);
}
/**
* Submits a request to update a feature using the POST protocol binding.
*
* @param id
* A feature identifier.
* @param featureType
* The qualified name of the feature type.
* @param properties
* A Map containing the feature properties to be updated.
* @return A Document representing the XML response entity.
*
* @see #updateFeature(Document, String, QName, Map, ProtocolBinding)
*/
public Document updateFeature(String id, QName featureType,
Map properties) {
Document req = WFSRequest.createRequestEntity(WFS2.TRANSACTION);
return updateFeature(req, id, featureType, properties,
ProtocolBinding.POST);
}
/**
* Submits a request to update a feature.
*
* @param req
* An empty wfs:Transaction request entity.
* @param id
* The GML identifier of the feature to be updated (gml:id
* attribute).
* @param featureType
* The type of the feature instance.
* @param properties
* A Map containing the feature properties to be updated
* (replaced). Each entry consists of a value reference (an XPath
* expression) and a value object. The value may be a Node
* representing a complex property value; otherwise it is treated
* as a simple value by calling the object's toString() method.
* @param binding
* The ProtocolBinding to use.
* @return A Document representing the XML response entity, or {@code null}
* if the response doesn't contain one.
*/
public Document updateFeature(Document req, String id, QName featureType,
Map properties, ProtocolBinding binding) {
Element update = req.createElementNS(Namespaces.WFS, "Update");
update.setPrefix("wfs");
req.getDocumentElement().appendChild(update);
WFSRequest.setTypeName(update, featureType);
for (Map.Entry property : properties.entrySet()) {
Element prop = req.createElementNS(Namespaces.WFS, "Property");
prop.setPrefix("wfs");
Element valueRef = req.createElementNS(Namespaces.WFS,
"ValueReference");
valueRef.setTextContent(property.getKey());
valueRef.setPrefix("wfs");
prop.appendChild(valueRef);
Element value = req.createElementNS(Namespaces.WFS, "Value");
value.setPrefix("wfs");
if (Node.class.isInstance(property.getValue())) {
value.appendChild((Node) property.getValue());
} else {
value.setTextContent(property.getValue().toString());
}
prop.appendChild(value);
update.appendChild(prop);
}
Element filter = WFSRequest.newResourceIdFilter(id);
update.appendChild(req.adoptNode(filter));
if (TestSuiteLogger.isLoggable(Level.FINE)) {
TestSuiteLogger.log(Level.FINE, XMLUtils.writeNodeToString(req));
}
return executeTransaction(req, binding);
}
/**
* Submits an HTTP request message. For GET requests the XML request entity
* is serialized to its corresponding KVP string format and added to the
* query component of the Request-URI. For SOAP requests that adhere to the
* "Request-Response" message exchange pattern, the outbound message entity
* is a SOAP envelope containing the standard XML request in the body.
*
* @param entity
* An XML representation of the request entity.
* @param binding
* The {@link ProtocolBinding} to use.
* @param endpoint
* The service endpoint.
* @return A ClientResponse object representing the response message.
*/
public ClientResponse submitRequest(Source entity, ProtocolBinding binding,
URI endpoint) {
WebResource resource = client.resource(endpoint);
WebResource.Builder builder = resource.accept(
MediaType.APPLICATION_XML_TYPE,
MediaType.valueOf(WFS2.APPLICATION_SOAP));
LOGR.log(Level.FINE, String.format("Submitting %s request to URI %s",
binding, resource.getURI()));
ClientResponse response = null;
switch (binding) {
case GET:
String queryString = WFSRequest.transformEntityToKVP(entity);
URI requestURI = UriBuilder.fromUri(resource.getURI())
.replaceQuery(queryString).build();
LOGR.log(Level.FINE, String.format("Request URI: %s", requestURI));
resource = resource.uri(requestURI);
response = resource.get(ClientResponse.class);
break;
case POST:
response = builder.type(MediaType.APPLICATION_XML_TYPE).post(
ClientResponse.class, entity);
break;
case SOAP:
Document soapEnv = WFSRequest.wrapEntityInSOAPEnvelope(entity,
WFS2.SOAP_VERSION);
response = builder.type(MediaType.valueOf(WFS2.APPLICATION_SOAP))
.post(ClientResponse.class, new DOMSource(soapEnv));
break;
default:
throw new IllegalArgumentException("Unsupported message binding: "
+ binding);
}
return response;
}
/**
* Submits a request using the specified message binding and the content of
* the given XML request entity.
*
* @param reqEntity
* A DOM Document representing the content of the request
* message.
* @param binding
* The ProtocolBinding to use; may be {@link ProtocolBinding#ANY}
* if any supported binding can be used.
* @return A ClientResponse object representing the response message.
*/
public ClientResponse submitRequest(Document reqEntity,
ProtocolBinding binding) {
String requestName = reqEntity.getDocumentElement().getLocalName();
Map endpoints = ServiceMetadataUtils.getRequestEndpoints(
this.wfsMetadata, requestName);
if (null == endpoints) {
throw new IllegalArgumentException(
"No HTTP method bindings found for " + requestName);
}
if ((null == binding) || binding.equals(ProtocolBinding.ANY)) {
String methodName = endpoints.keySet().iterator().next();
binding = Enum.valueOf(ProtocolBinding.class, methodName);
}
// SOAP Request-Response MEP bound to HTTP POST
String httpMethod = (binding == ProtocolBinding.SOAP) ? ProtocolBinding.POST
.name() : binding.name();
return submitRequest(new DOMSource(reqEntity), binding,
endpoints.get(httpMethod));
}
/**
* Retrieves a complete representation of the capabilities document from the
* WFS implementation described by the service metadata.
*
* @return A Document containing the response to a GetCapabilities request,
* or {@code null} if one could not be obtained.
*/
public Document getCapabilities() {
if (null == this.wfsMetadata) {
throw new IllegalStateException(
"Service description is unavailable.");
}
URI endpoint = ServiceMetadataUtils.getOperationEndpoint(
this.wfsMetadata, WFS2.GET_CAPABILITIES, ProtocolBinding.GET);
WebResource resource = client.resource(endpoint);
MultivaluedMap queryParams = new MultivaluedMapImpl();
queryParams.add(WFS2.REQUEST_PARAM, WFS2.GET_CAPABILITIES);
queryParams.add(WFS2.SERVICE_PARAM, WFS2.SERVICE_TYPE_CODE);
queryParams.add(WFS2.VERSION_PARAM, WFS2.VERSION);
return resource.queryParams(queryParams).get(Document.class);
}
/**
* Returns a protocol binding suitable for transaction requests. Any binding
* advertised in the service capabilities document is returned.
*
* @return A supported ProtocolBinding instance (POST or SOAP).
*/
public ProtocolBinding getAnyTransactionBinding() {
Set trxBindings = ServiceMetadataUtils
.getOperationBindings(this.wfsMetadata, WFS2.TRANSACTION);
return trxBindings.iterator().next();
}
/**
* Executes a WFS transaction.
*
* @param request
* A Document node representing a wfs:Transaction request entity.
* @param binding
* The ProtocolBinding to use
* @return A Document node representing the response entity.
*/
Document executeTransaction(Document request, ProtocolBinding binding) {
if (binding == ProtocolBinding.ANY) {
binding = getAnyTransactionBinding();
}
URI endpoint = ServiceMetadataUtils.getOperationEndpoint(
this.wfsMetadata, WFS2.TRANSACTION, binding);
if (null == endpoint.getScheme()) {
throw new IllegalArgumentException(
"No Transaction endpoint found for binding " + binding);
}
LOGR.log(Level.FINE, String.format(
"Submitting request entity to URI %s \n%s", endpoint,
XMLUtils.writeNodeToString(request)));
ClientResponse rsp = submitRequest(new DOMSource(request), binding,
endpoint);
Document entity = null;
if (rsp.hasEntity()) {
entity = rsp.getEntity(Document.class);
}
return entity;
}
/**
* Submits the given request entity and returns the response entity as a DOM
* Document.
*
* @param request
* An XML representation of the request entity; the actual
* request depends on the message binding in use.
* @param binding
* The ProtocolBinding to use (GET, POST, or SOAP).
* @return A DOM Document containing the response entity, or {@code null} if
* the request failed or the message body could not be parsed.
*/
Document retrieveXMLResponseEntity(Document request, ProtocolBinding binding) {
if (LOGR.isLoggable(Level.FINE)) {
LOGR.fine("Request entity:\n" + XMLUtils.writeNodeToString(request));
}
URI endpoint = ServiceMetadataUtils.getOperationEndpoint(
this.wfsMetadata, request.getDocumentElement().getLocalName(),
binding);
ClientResponse rsp = submitRequest(new DOMSource(request), binding,
endpoint);
Document rspEntity = null;
if (rsp.hasEntity()) {
MediaType mediaType = rsp.getType();
if (!mediaType.getSubtype().endsWith("xml")) {
throw new RuntimeException("Did not receive an XML entity: "
+ mediaType);
}
rspEntity = rsp.getEntity(Document.class);
if (LOGR.isLoggable(Level.FINE)) {
LOGR.fine("Response entity:\n"
+ XMLUtils.writeNodeToString(rspEntity));
}
}
return rspEntity;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy