Issue #2539 added GZIP support to WFS REST

small code cleanup in providers


Former-commit-id: 943310ed8b34f62d73dee1dff32d5c3b9f424e3c
This commit is contained in:
Brian Clements 2013-11-11 10:04:55 -06:00
parent 0dbc31b756
commit 36a0be2db1
9 changed files with 448 additions and 77 deletions

View file

@ -0,0 +1,91 @@
/**
* This software was developed and / or modified by Raytheon Company,
* pursuant to Contract DG133W-05-CQ-1067 with the US Government.
*
* U.S. EXPORT CONTROLLED TECHNICAL DATA
* This software product contains export-restricted data whose
* export/transfer/disclosure is restricted by U.S. law. Dissemination
* to non-U.S. persons whether in the United States or abroad requires
* an export license or other authorization.
*
* Contractor Name: Raytheon Company
* Contractor Address: 6825 Pine Street, Suite 340
* Mail Stop B8
* Omaha, NE 68106
* 402.291.0100
*
* See the AWIPS II Master Rights File ("Master Rights File.pdf") for
* further licensing information.
**/
package com.raytheon.uf.edex.ogc.common.http;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Parses Accept-Encoding headers for HTTP requests
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Nov 8, 2013 2539 bclement Initial creation
*
* </pre>
*
* @author bclement
* @version 1.0
*/
public class AcceptHeaderParser implements Iterable<AcceptHeaderValue> {
private static final Pattern ENCODING_PATTERN = Pattern
.compile("([^;, \\t]+)\\s*(;\\s*(q\\s*=\\s*([0-9.]+)))?");
private final String input;
/**
*
*/
public AcceptHeaderParser(String input) {
this.input = input;
}
/*
* (non-Javadoc)
*
* @see java.lang.Iterable#iterator()
*/
@Override
public Iterator<AcceptHeaderValue> iterator() {
final Matcher matcher = ENCODING_PATTERN.matcher(input);
return new Iterator<AcceptHeaderValue>() {
@Override
public boolean hasNext() {
return matcher.find();
}
@Override
public AcceptHeaderValue next() {
String enc = matcher.group(1);
String qvalStr = matcher.group(4);
if (qvalStr == null) {
return new AcceptHeaderValue(enc);
} else {
return new AcceptHeaderValue(enc,
Double.parseDouble(qvalStr));
}
}
@Override
public void remove() {
throw new UnsupportedOperationException(
"Remove not supported for " + this.getClass());
}
};
}
}

View file

@ -0,0 +1,77 @@
/**
* This software was developed and / or modified by Raytheon Company,
* pursuant to Contract DG133W-05-CQ-1067 with the US Government.
*
* U.S. EXPORT CONTROLLED TECHNICAL DATA
* This software product contains export-restricted data whose
* export/transfer/disclosure is restricted by U.S. law. Dissemination
* to non-U.S. persons whether in the United States or abroad requires
* an export license or other authorization.
*
* Contractor Name: Raytheon Company
* Contractor Address: 6825 Pine Street, Suite 340
* Mail Stop B8
* Omaha, NE 68106
* 402.291.0100
*
* See the AWIPS II Master Rights File ("Master Rights File.pdf") for
* further licensing information.
**/
package com.raytheon.uf.edex.ogc.common.http;
/**
* Represents a single HTTP accept(-encoding) header encoding with weight value
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Nov 8, 2013 2539 bclement Initial creation
*
* </pre>
*
* @author bclement
* @version 1.0
*/
public class AcceptHeaderValue {
private final String encoding;
private final double qvalue;
public AcceptHeaderValue(String encoding, double qvalue) {
this.encoding = encoding;
if (qvalue < 0 || qvalue > 1.0) {
throw new IllegalArgumentException(
"qvalue must be between 0 and 1.0");
}
this.qvalue = qvalue;
}
public AcceptHeaderValue(String encoding) {
this(encoding, 1.0);
}
/**
* @return the encoding
*/
public String getEncoding() {
return encoding;
}
/**
* @return the qvalue
*/
public double getQvalue() {
return qvalue;
}
/**
* @return true if encoding is acceptable
*/
public boolean isAcceptable(){
return qvalue != 0;
}
}

View file

@ -0,0 +1,97 @@
/**
* This software was developed and / or modified by Raytheon Company,
* pursuant to Contract DG133W-05-CQ-1067 with the US Government.
*
* U.S. EXPORT CONTROLLED TECHNICAL DATA
* This software product contains export-restricted data whose
* export/transfer/disclosure is restricted by U.S. law. Dissemination
* to non-U.S. persons whether in the United States or abroad requires
* an export license or other authorization.
*
* Contractor Name: Raytheon Company
* Contractor Address: 6825 Pine Street, Suite 340
* Mail Stop B8
* Omaha, NE 68106
* 402.291.0100
*
* See the AWIPS II Master Rights File ("Master Rights File.pdf") for
* further licensing information.
**/
package com.raytheon.uf.edex.ogc.common.http;
/**
* Exception for when an OGC request fails at the HTTP level. Contains HTTP
* error code for response.
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Nov 11, 2013 2539 bclement Initial creation
*
* </pre>
*
* @author bclement
* @version 1.0
*/
public class OgcHttpErrorException extends Exception {
private static final long serialVersionUID = -452797331391106559L;
private final int code;
/**
*
*/
public OgcHttpErrorException(int code) {
super();
this.code = code;
}
/**
* @param message
* @param cause
* @param enableSuppression
* @param writableStackTrace
*/
public OgcHttpErrorException(int code, String message, Throwable cause,
boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
this.code = code;
}
/**
* @param message
* @param cause
*/
public OgcHttpErrorException(int code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
/**
* @param message
*/
public OgcHttpErrorException(int code, String message) {
super(message);
this.code = code;
}
/**
* @param cause
*/
public OgcHttpErrorException(int code, Throwable cause) {
super(cause);
this.code = code;
}
/**
* @return the error status code
*/
public int getCode() {
return code;
}
}

View file

@ -22,11 +22,15 @@
*/
package com.raytheon.uf.edex.ogc.common.http;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import javax.ws.rs.core.Response.Status;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
@ -42,6 +46,7 @@ import com.raytheon.uf.edex.ogc.common.OgcResponse;
import com.raytheon.uf.edex.ogc.common.OgcResponse.TYPE;
import com.raytheon.uf.edex.ogc.common.output.IOgcHttpResponse;
import com.raytheon.uf.edex.ogc.common.output.OgcResponseOutput;
import com.raytheon.uf.edex.ogc.common.output.ServletOgcResponse;
/**
@ -53,7 +58,8 @@ import com.raytheon.uf.edex.ogc.common.output.OgcResponseOutput;
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* 2011 bclement Initial creation
* 2011 bclement Initial creation
* Nov 11, 2013 2539 bclement added accept encoding parsing
*
* </pre>
*
@ -72,6 +78,8 @@ public abstract class OgcHttpHandler {
public static final String ACCEPT_VERSIONS_HEADER = "acceptversions";
public static final String ACCEPT_ENC_HEADER = "accept-encoding";
public abstract void handle(OgcHttpRequest request);
private IUFStatusHandler log = UFStatus.getHandler(this.getClass());
@ -285,4 +293,67 @@ public abstract class OgcHttpHandler {
return rval;
}
/**
* Check if accept encoding header is present and modify response
* accordingly.
*
* @param headers
* @param response
* @throws OgcException
* on internal server error
* @throws OgcHttpErrorException
* on HTTP protocol error condition
*/
protected void acceptEncodingCheck(Map<String, Object> headers,
ServletOgcResponse response) throws OgcException,
OgcHttpErrorException {
Object obj = headers.get(ACCEPT_ENC_HEADER);
if (obj == null) {
// omitted accept encoding signifies that they accept anything
return;
}
if (!(obj instanceof String)) {
log.error("Unsupported header type encountered: " + obj.getClass());
throw new OgcException(Code.InternalServerError);
}
String encoding = (String) obj;
boolean gzipAcceptable = false;
boolean anyAcceptable = false;
for (AcceptHeaderValue value : new AcceptHeaderParser(encoding)) {
if (value.getEncoding().toLowerCase().contains("gzip")
&& value.isAcceptable()) {
gzipAcceptable = true;
} else if (value.getEncoding().trim().equals("*")) {
anyAcceptable = true;
}
}
if (gzipAcceptable) {
response.enableGzip();
} else if (!anyAcceptable) {
throw new OgcHttpErrorException(406);
}
}
/**
* Output HTTP protocol error
*
* @param response
* @param errorCode
* @throws IOException
*/
protected void outputHttpError(ServletOgcResponse response, int errorCode)
throws IOException {
response.setStatus(errorCode);
response.setContentType("text/plain");
Writer writer = null;
try {
writer = new OutputStreamWriter(response.getOutputStream());
writer.write(Status.fromStatusCode(errorCode).toString());
} finally {
if (writer != null) {
writer.flush();
writer.close();
}
}
}
}

View file

@ -11,6 +11,7 @@ package com.raytheon.uf.edex.ogc.common.output;
import java.io.IOException;
import java.io.OutputStream;
import java.util.zip.GZIPOutputStream;
import javax.servlet.http.HttpServletResponse;
@ -24,6 +25,7 @@ import javax.servlet.http.HttpServletResponse;
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Jan 7, 2013 bclement Initial creation
* Nov 11, 2013 2539 bclement added GZIP support
*
* </pre>
*
@ -32,8 +34,14 @@ import javax.servlet.http.HttpServletResponse;
*/
public class ServletOgcResponse implements IOgcHttpResponse {
public static final String CONTENT_ENC_HEADER = "Content-Encoding";
private final HttpServletResponse response;
private boolean gzip = false;
private volatile OutputStream out = null;
/**
*
*/
@ -46,7 +54,13 @@ public class ServletOgcResponse implements IOgcHttpResponse {
*/
@Override
public OutputStream getOutputStream() throws IOException {
return response.getOutputStream();
if (out == null) {
out = response.getOutputStream();
if (gzip) {
out = new GZIPOutputStream(out);
}
}
return out;
}
/* (non-Javadoc)
@ -73,4 +87,20 @@ public class ServletOgcResponse implements IOgcHttpResponse {
response.setCharacterEncoding(encoding);
}
/**
* @return true if response Content-Encoding is GZIP
*/
public boolean isGzip() {
return gzip;
}
/**
* Turn on GZIP Content Encoding
*
*/
public void enableGzip() {
response.addHeader(CONTENT_ENC_HEADER, "gzip");
this.gzip = true;
}
}

View file

@ -45,9 +45,9 @@ import com.raytheon.uf.edex.ogc.common.OgcException.Code;
import com.raytheon.uf.edex.ogc.common.OgcResponse;
import com.raytheon.uf.edex.ogc.common.Version;
import com.raytheon.uf.edex.ogc.common.http.EndpointInfo;
import com.raytheon.uf.edex.ogc.common.http.OgcHttpErrorException;
import com.raytheon.uf.edex.ogc.common.http.OgcHttpHandler;
import com.raytheon.uf.edex.ogc.common.http.OgcHttpRequest;
import com.raytheon.uf.edex.ogc.common.output.IOgcHttpResponse;
import com.raytheon.uf.edex.ogc.common.output.OgcResponseOutput;
import com.raytheon.uf.edex.ogc.common.output.ServletOgcResponse;
@ -106,7 +106,8 @@ public class WfsHttpHandler extends OgcHttpHandler {
protected void handleInternal(OgcHttpRequest req) throws Exception {
Map<String, Object> headers = req.getHeaders();
IOgcHttpResponse response = new ServletOgcResponse(req.getResponse());
ServletOgcResponse response = new ServletOgcResponse(req.getResponse());
HttpServletRequest httpReq = req.getRequest();
int port = httpReq.getLocalPort();
String host = httpReq.getServerName();
@ -114,6 +115,13 @@ public class WfsHttpHandler extends OgcHttpHandler {
// TODO dynamic path and protocol
EndpointInfo info = new EndpointInfo(host, port, path);
try {
try {
acceptEncodingCheck(headers, response);
} catch (OgcHttpErrorException e) {
// there was a problem with the acceptable encodings
outputHttpError(response, e.getCode());
return;
}
if (req.isPost()) {
InputStream is = req.getInputStream();
BufferedInputStream bufin = new BufferedInputStream(is);

View file

@ -11,6 +11,7 @@ package com.raytheon.uf.edex.wfs.provider;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
@ -27,9 +28,16 @@ import com.raytheon.uf.edex.ogc.common.OgcBoundingBox;
import com.raytheon.uf.edex.ogc.common.OgcException;
import com.raytheon.uf.edex.ogc.common.OgcNamespace;
import com.raytheon.uf.edex.ogc.common.OgcPrefix;
import com.raytheon.uf.edex.ogc.common.OgcResponse;
import com.raytheon.uf.edex.ogc.common.OgcTimeRange;
import com.raytheon.uf.edex.ogc.common.http.MimeType;
import com.raytheon.uf.edex.ogc.common.http.OgcHttpHandler;
import com.raytheon.uf.edex.ogc.common.output.IOgcHttpResponse;
import com.raytheon.uf.edex.ogc.common.output.OgcResponseOutput;
import com.raytheon.uf.edex.wfs.IWfsProvider;
import com.raytheon.uf.edex.wfs.WfsException;
import com.raytheon.uf.edex.wfs.WfsException.Code;
import com.raytheon.uf.edex.wfs.reg.WfsRegistryImpl;
import com.raytheon.uf.edex.wfs.request.QualifiedName;
import com.raytheon.uf.edex.wfs.request.SortBy;
import com.raytheon.uf.edex.wfs.request.SortBy.Order;
@ -45,6 +53,7 @@ import com.raytheon.uf.edex.wfs.request.SortBy.Order;
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Oct 25, 2012 bclement Initial creation
* Nov 11, 2013 2539 bclement moved registry/marshal from children
*
* </pre>
*
@ -122,6 +131,17 @@ public abstract class AbstractWfsProvider implements IWfsProvider {
};
protected final WfsRegistryImpl registry;
protected final IUFStatusHandler log = UFStatus.getHandler(this.getClass());
/**
*
*/
public AbstractWfsProvider(WfsRegistryImpl registry) {
this.registry = registry;
}
/**
* @param nsmap
* @param string
@ -270,4 +290,44 @@ public abstract class AbstractWfsProvider implements IWfsProvider {
}
}
/**
* Marshal object through response. Response cannot be reused after this
* method is called.
*
* @param jaxbobject
* @param mimeType
* @param response
* @throws Exception
* on unrecoverable error attempting to send response
*/
protected void marshalResponse(Object jaxbobject, MimeType mimeType,
IOgcHttpResponse response) throws Exception {
OutputStream out = null;
try {
out = response.getOutputStream();
response.setContentType(mimeType.toString());
registry.marshal(jaxbobject, out);
} catch (Exception e) {
log.error("Unable to marshal WFS response", e);
OgcResponse err = getError(new WfsException(
Code.OperationProcessingFailed), null);
OgcResponseOutput.sendText(err, response, out);
} finally {
if (out != null) {
out.flush();
out.close();
}
}
}
/**
* Create an error response
*
* @param e
* @param exceptionFormat
* @return
*/
public abstract OgcResponse getError(WfsException e,
MimeType exceptionFormat);
}

View file

@ -45,8 +45,6 @@ import org.opengis.feature.simple.SimpleFeature;
import org.springframework.context.ApplicationContext;
import org.w3.xmlschema.Schema;
import com.raytheon.uf.common.status.IUFStatusHandler;
import com.raytheon.uf.common.status.UFStatus;
import com.raytheon.uf.edex.core.EDEXUtil;
import com.raytheon.uf.edex.ogc.common.OgcException;
import com.raytheon.uf.edex.ogc.common.OgcOperationInfo;
@ -89,6 +87,7 @@ import com.raytheon.uf.edex.wfs.soap2_0_0.util.DescribeFeatureTypeResponseType;
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Apr 22, 2011 bclement Initial creation
* Nov 11, 2013 2539 bclement moved registry/marshal to parent
*
* </pre>
*
@ -97,14 +96,10 @@ import com.raytheon.uf.edex.wfs.soap2_0_0.util.DescribeFeatureTypeResponseType;
*/
public class Wfs1_1_0Provider extends AbstractWfsProvider {
protected final IUFStatusHandler log = UFStatus.getHandler(this.getClass());
public static final String version = "1.1.0";
public static final MimeType GML_MIME = GmlUtils.GML311_OLD_TYPE;
protected final WfsRegistryImpl registry;
protected final Capabilities capabilities;
protected final Gml31FeatureFetcher features;
@ -116,18 +111,21 @@ public class Wfs1_1_0Provider extends AbstractWfsProvider {
protected final ObjectFactory wfsFactory = new ObjectFactory();
public Wfs1_1_0Provider(WfsRegistryImpl registry) {
super(registry);
this.capabilities = new Capabilities(registry);
this.features = new Gml31FeatureFetcher(registry);
this.describer = new FeatureDescriber(registry, this);
this.registry = registry;
this.transactor = new Transactor();
}
/**
* Unit tests
*/
protected Wfs1_1_0Provider() {
super(null);
this.capabilities = null;
this.features = null;
this.describer = null;
this.registry = null;
this.transactor = null;
}
@ -338,35 +336,6 @@ public class Wfs1_1_0Provider extends AbstractWfsProvider {
return rval;
}
/**
* Marshal object through response. Response cannot be reused after this
* method is called.
*
* @param jaxbobject
* @param mimeType
* @param response
* @throws Exception
* on unrecoverable error attempting to send response
*/
protected void marshalResponse(Object jaxbobject, MimeType mimeType,
IOgcHttpResponse response) throws Exception {
OutputStream out = null;
try {
out = response.getOutputStream();
response.setContentType(mimeType.toString());
registry.marshal(jaxbobject, response.getOutputStream());
} catch (Exception e) {
log.error("Unable to marshal WFS response", e);
OgcResponse err = getError(new WfsException(
Code.OperationProcessingFailed), null);
OgcResponseOutput.sendText(err, response, out);
} finally {
if (out != null) {
out.close();
}
}
}
/**
* Get features as JAXB object
*

View file

@ -67,8 +67,6 @@ import org.w3.xmlschema.Schema;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.raytheon.uf.common.status.IUFStatusHandler;
import com.raytheon.uf.common.status.UFStatus;
import com.raytheon.uf.edex.core.EDEXUtil;
import com.raytheon.uf.edex.ogc.common.OgcException;
import com.raytheon.uf.edex.ogc.common.OgcOperationInfo;
@ -119,6 +117,7 @@ import com.raytheon.uf.edex.wfs.util.XMLGregorianCalendarConverter;
* ------------ ---------- ----------- --------------------------
* Oct 17, 2012 bclement Initial creation
* Sep 18, 2013 #411 skorolev Added required RESPONSE METADATA
* Nov 11, 2013 2539 bclement moved registry/marshal to parent
*
* </pre>
*
@ -128,14 +127,10 @@ import com.raytheon.uf.edex.wfs.util.XMLGregorianCalendarConverter;
public class Wfs2_0_0Provider extends AbstractWfsProvider implements
IStoredQueryCallback {
protected final IUFStatusHandler log = UFStatus.getHandler(this.getClass());
public static final String version = "2.0.0";
public static final MimeType GML_MIME = GmlUtils.GML32_TYPE;
protected WfsRegistryImpl registry;
protected final Capabilities capabilities;
protected final Gml32FeatureFetcher features;
@ -151,10 +146,10 @@ public class Wfs2_0_0Provider extends AbstractWfsProvider implements
protected final NamespaceContext nsContext;
public Wfs2_0_0Provider(WfsRegistryImpl registry) {
super(registry);
this.capabilities = new Capabilities(registry);
this.features = new Gml32FeatureFetcher(registry);
this.describer = new FeatureDescriber(registry, this);
this.registry = registry;
// this name must match preloaded store in utility directory
this.queryStore = new FileSystemQueryStore(registry, "wfsQueryStore");
Map<String, Object> reverseNsMap = new HashMap<String, Object>(
@ -169,10 +164,10 @@ public class Wfs2_0_0Provider extends AbstractWfsProvider implements
* Unit tests
*/
protected Wfs2_0_0Provider() {
super(null);
this.capabilities = null;
this.features = null;
this.describer = null;
this.registry = null;
this.queryStore = null;
this.nsContext = null;
}
@ -384,34 +379,7 @@ public class Wfs2_0_0Provider extends AbstractWfsProvider implements
return rval;
}
/**
* Marshal object through response. Response cannot be reused after this
* method is called.
*
* @param jaxbobject
* @param mimeType
* @param response
* @throws Exception
* on unrecoverable error attempting to send response
*/
protected void marshalResponse(Object jaxbobject, MimeType mimeType,
IOgcHttpResponse response) throws Exception {
OutputStream out = null;
try {
out = response.getOutputStream();
response.setContentType(mimeType.toString());
registry.marshal(jaxbobject, response.getOutputStream());
} catch (Exception e) {
log.error("Unable to marshal WFS response", e);
OgcResponse err = getError(new WfsException(
Code.OperationProcessingFailed), null);
OgcResponseOutput.sendText(err, response, out);
} finally {
if (out != null) {
out.close();
}
}
}
/**
* Get features as JAXB object