/* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved. * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.gss; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URL; import java.nio.charset.Charset; import java.util.Iterator; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.namespace.QName; import net.opengis.ows10.ExceptionReportType; import net.opengis.ows10.ExceptionType; import net.opengis.wfs.DeleteElementType; import net.opengis.wfs.InsertElementType; import net.opengis.wfs.TransactionType; import net.opengis.wfs.UpdateElementType; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpMethod; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.RequestEntity; import org.apache.commons.httpclient.methods.StringRequestEntity; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.NamespaceInfo; import org.geoserver.gss.CentralRevisionsType.LayerRevision; import org.geoserver.gss.xml.GSS; import org.geoserver.gss.xml.GSSConfiguration; import org.geotools.util.logging.Logging; import org.geotools.xml.Encoder; import org.geotools.xml.Parser; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.type.Name; import org.xml.sax.helpers.NamespaceSupport; /** * A client that connects to a single Unit. * * @author Andrea Aime - OpenGeo * */ public class HTTPGSSClient implements GSSClient { static final Logger LOGGER = Logging.getLogger(HTTPGSSClient.class); HttpClient client; URL address; String username; String password; GSSConfiguration configuration; Catalog catalog; public HTTPGSSClient(HttpClient client, GSSConfiguration configuration, Catalog catalog, URL gssServiceURL, String username, String password) { if (gssServiceURL.getPath().endsWith("/")) { try { String external = gssServiceURL.toExternalForm(); this.address = new URL(external.substring(0, external.length() - 1)); } catch (MalformedURLException e) { throw new RuntimeException("Unexpected error normalizing GSS url" + e); } } else { this.address = gssServiceURL; } this.configuration = configuration; this.client = client; this.username = username; this.password = password; this.catalog = catalog; } public long getCentralRevision(QName layerName) throws IOException { // execute the GetCentralRevision call GetMethod method = new GetMethod(address + "?service=GSS&version=1.0.0&request=GetCentralRevision&typeName=" + prefixedName(layerName)); Object response = executeMethod(method); // interpret the parsed response if (response instanceof CentralRevisionsType) { CentralRevisionsType cr = (CentralRevisionsType) response; for (LayerRevision lr : cr.getLayerRevisions()) { if (layerName.equals(lr.getTypeName())) { return lr.getCentralRevision(); } } throw new IOException("Response to GetCentralRevision received, but it " + "did not contain a central revision for " + layerName); } else { if (response == null) { throw new IOException("The response was parsed to a null object"); } throw new IOException("The response was parsed to an unrecognized object type: " + response.getClass()); } } String prefixedName(QName layerName) { return layerName.getPrefix() + ":" + layerName.getLocalPart(); } public GetDiffResponseType getDiff(GetDiffType getDiff) throws IOException { // prepare the encoder Encoder encoder = new Encoder(configuration, configuration.getXSD().getSchema()); QName layerName = getDiff.getTypeName(); encoder.getNamespaces().declarePrefix(layerName.getPrefix(), layerName.getNamespaceURI()); encoder.setEncoding(Charset.forName("UTF-8")); // prepare POST request PostMethod method = new PostMethod(address.toExternalForm()); method.setContentChunked(true); method.setRequestEntity(new XMLEntity(getDiff, GSS.GetDiff, encoder)); // execute the request and interpret the response Object response = executeMethod(method); if (response instanceof GetDiffResponseType) { return (GetDiffResponseType) response; } else { if (response == null) { throw new IOException("The response was parsed to a null object"); } throw new IOException("The response was parsed to an unrecognized object type: " + response.getClass()); } } public void postDiff(PostDiffType postDiff) throws IOException { // prepare the encoder Encoder encoder = buildEncoderForTransaction(postDiff.getTransaction()); // prepare POST request PostMethod method = new PostMethod(address.toExternalForm()); method.setContentChunked(true); method.setRequestEntity(new XMLEntity(postDiff, GSS.PostDiff, encoder)); // execute the request and interpret the parsed response Object response = executeMethod(method); if (response instanceof PostDiffResponseType) { // that's fine, it's like a placeholder for now } else { if (response == null) { throw new IOException("The response was parsed to a null object"); } throw new IOException("The response was parsed to an unrecognized object type: " + response.getClass()); } } /** * Executes the http method, checks the response status, parses the response and returns it. * Will throw an exception in case of communication errors, error codes, or service exceptions * * @throws IOException */ Object executeMethod(HttpMethod method) throws IOException { Object response; try { if(LOGGER.isLoggable(Level.FINE)) { if(method instanceof GetMethod) { GetMethod gm = (GetMethod) method; LOGGER.fine("Sending GET request:\n " + method.getURI()); } else if(method instanceof PostMethod) { PostMethod pm = (PostMethod) method; XMLEntity entity = (XMLEntity) pm.getRequestEntity(); String request = entity.toString(); LOGGER.fine("Sending POST request:\n " + method.getURI() + "\n" + request); // ugly, but Encoder cannot be reused, so we have to set a new entity // that uses the already generated string pm.setRequestEntity(new StringRequestEntity(request, "text/xml", "UTF-8")); } else { LOGGER.fine("Sending unknown method type : " + method); } } // plain execution int statusCode = client.executeMethod(method); // check the HTTP status if (statusCode != HttpStatus.SC_OK) { throw new IOException("HTTP client returned with code " + statusCode); } // parse the response Parser parser = new Parser(configuration); parser.setStrict(true); parser.setFailOnValidationError(true); InputStream is; if(LOGGER.isLoggable(Level.FINE)) { String responseString = method.getResponseBodyAsString(); LOGGER.log(Level.FINE, "Response from Unit:\n" + responseString); is = new ByteArrayInputStream(responseString.getBytes()); } else { is = method.getResponseBodyAsStream(); } response = parser.parse(is); } catch (Exception e) { throw (IOException) new IOException("Error occurred while executing " + "a call to the Unit").initCause(e); } finally { method.releaseConnection(); } // convert a service exception into an IOException if necessary if (response instanceof ExceptionReportType) { ExceptionReportType report = (ExceptionReportType) response; StringBuilder sb = new StringBuilder("The Unit service reported a failure: "); for (Iterator it = report.getException().iterator(); it.hasNext();) { ExceptionType et = (ExceptionType) it.next(); for (Iterator eit = et.getExceptionText().iterator(); eit.hasNext();) { String text = (String) eit.next(); sb.append(text); if (eit.hasNext()) sb.append(". "); } } throw new IOException(sb.toString()); } return response; } /** * Builds a XML encoder for the specified transaction. The code will declare all * prefix/namespace URI associations necessary for the elements in the transaction */ Encoder buildEncoderForTransaction(TransactionType changes) throws IOException { Encoder encoder = new Encoder(configuration, configuration.getXSD().getSchema()); // try to declare all namespace prefixes properly NamespaceSupport namespaces = encoder.getNamespaces(); List<DeleteElementType> deletes = changes.getDelete(); List<UpdateElementType> updates = changes.getUpdate(); List<InsertElementType> inserts = changes.getInsert(); for (DeleteElementType delete : deletes) { QName typeName = delete.getTypeName(); namespaces.declarePrefix(typeName.getPrefix(), typeName.getNamespaceURI()); } for (UpdateElementType update : updates) { QName typeName = update.getTypeName(); namespaces.declarePrefix(typeName.getPrefix(), typeName.getNamespaceURI()); } for (InsertElementType insert : inserts) { List<SimpleFeature> features = insert.getFeature(); for (SimpleFeature feature : features) { Name typeName = feature.getType().getName(); NamespaceInfo nsi = catalog.getNamespaceByURI(typeName.getNamespaceURI()); if (nsi != null) { namespaces.declarePrefix(nsi.getPrefix(), nsi.getURI()); } } } encoder.setEncoding(Charset.forName("UTF-8")); return encoder; } /** * An XML entitity based * * @author aaime * */ static class XMLEntity implements RequestEntity { Encoder encoder; Object object; QName element; public XMLEntity(Object object, QName element, Encoder encoder) { super(); this.encoder = encoder; this.object = object; this.element = element; } public long getContentLength() { return -1; } public String getContentType() { return "text/xml"; } public boolean isRepeatable() { return true; } public void writeRequest(OutputStream os) throws IOException { encoder.encode(object, element, os); } @Override public String toString() { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); encoder.setIndenting(true); encoder.encode(object, element, bos); return bos.toString(); } catch(IOException e) { return "XMLEntity toString failed: " + e.getMessage(); } } } }