Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/update-capabilities-automatically #86

Merged
merged 59 commits into from
Nov 14, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
3780ef7
Add UpdateLayerCapabilities ActionHandler for WMS and WMTS layers
jampukka Aug 1, 2017
45a78b4
Tiny refactoring
jampukka Aug 1, 2017
338220d
Delete as unused
jampukka Aug 1, 2017
47e9924
Add few logging statements
jampukka Aug 1, 2017
3257290
Merge branch 'develop' into feature/timeseries-capabilities
jampukka Aug 10, 2017
39d10f1
Temp commit
jampukka Aug 10, 2017
88093a3
Merge remote-tracking branch 'oskari/develop' into feature/timeseries…
jampukka Aug 14, 2017
a8be392
Delete InMemoryImpl
jampukka Aug 15, 2017
210f6bb
Change OskariLayerCapabilities to immutable, make necessary changes t…
jampukka Aug 16, 2017
8c7e2cf
Change type of OskariLayerCapabilities.created and updated from java.…
jampukka Aug 16, 2017
02a797d
Create a separate class OskariLayerCapabilitiesDraft for non-persiste…
jampukka Aug 16, 2017
5da1c3b
Rename CapabilitiesInsertInfo to OskariLayerCapabilitiesInsertInfo
jampukka Aug 16, 2017
a6eec6a
Update CapabilitiesCacheServiceMock to reflect changes in OskariLayer…
jampukka Aug 16, 2017
e884427
Fix issue with executing updates or inserts within @Select() annotati…
jampukka Aug 17, 2017
a5c2a01
Add ServiceExceptions to CapabilitiesCacheService
jampukka Aug 17, 2017
889b3cc
Remove OskariLayerCapabilitiesDraft, change OskariLayerCapabilities t…
jampukka Aug 21, 2017
4f450b6
Change OskariLayerCapabilities to mostly immutable
jampukka Aug 21, 2017
8fc0f6b
Fix Mock class
jampukka Aug 21, 2017
a3cc9be
Move logic that handles GetCapabilities information from SaveLayerHan…
jampukka Aug 23, 2017
b35b35e
Remove UpdateLayerCapabilitiesHandler not necessary with scheduled im…
jampukka Aug 23, 2017
a2a11c0
Remove unuseful test
jampukka Aug 23, 2017
5db326d
Add few utility methods
jampukka Aug 25, 2017
0ea589b
Add logic that checks that the response from service is a proper GetC…
jampukka Aug 25, 2017
860feba
Remove extra whitespace before linebreak
jampukka Aug 25, 2017
4389009
Tiny fixes
jampukka Sep 5, 2017
7f4c3e8
Fix imports
jampukka Sep 13, 2017
3da69c4
Fix imports part2
jampukka Sep 13, 2017
1ed5ad7
Use PARAM_SRS_NAME instead of "srs_name"
jampukka Sep 13, 2017
f8f8e31
validateCapabilities() now throws ServiceExceptions instead of return…
jampukka Sep 13, 2017
6439ff5
Remove as unused
jampukka Sep 13, 2017
457cb75
Merge oskari/develop
jampukka Sep 14, 2017
1f556de
Add newline to end of file, dont add new lines where not necessary
jampukka Sep 14, 2017
858ab19
Refactor WMTSCapabilitiesParser
jampukka Sep 14, 2017
2980b2d
Remove Collection.unmodifiableXXX() wrappers
jampukka Sep 15, 2017
8789012
Little clean ups
jampukka Sep 15, 2017
4067133
Log invalid TileMatrixLimits instead of throwing an exception, add as…
jampukka Sep 15, 2017
1dd9c04
Improve error handling
jampukka Sep 15, 2017
131cea5
Add support for setting the maxAge via property
jampukka Sep 15, 2017
c1dad75
Add a test, not really a unit testable class
jampukka Sep 15, 2017
c2c0546
Add this back, maybe its needed, who knows
jampukka Sep 15, 2017
b0a30ca
Fix the Property name
jampukka Sep 15, 2017
2e2a5b5
Prevent crashing while instantiating if DataSource is null
jampukka Sep 18, 2017
222f895
Throw NPE instead of ServiceRuntimeException, more fitting
jampukka Sep 18, 2017
915f6f1
Add backwards compatibility
jampukka Sep 18, 2017
e2e9054
Dont write version column, it doesnt exist yet
jampukka Sep 19, 2017
a783be4
Dont modify xslt parameter
jampukka Sep 19, 2017
a465886
Add service-capabilities-update to parents pom
jampukka Sep 19, 2017
b4a14cd
Add some logging to UpdateCapabilitiesJob
jampukka Sep 19, 2017
78886d6
Fix TileMatrixLimits
jampukka Sep 19, 2017
98c75c1
Dont both log and throw an exception with the same message. Dont retu…
jampukka Oct 2, 2017
a728738
Fix multiple issues, remove support for WMS layers for now
jampukka Oct 3, 2017
5b92460
Undo these unnecessary changes to make the branch more linear
jampukka Oct 3, 2017
dca36d0
Fix text to reflect the removal of wms services
jampukka Oct 3, 2017
1a68242
Move package, change OskariLayerCapabilities full constructor to public
jampukka Oct 3, 2017
90872be
Move package, change OskariLayerCapabilities full constructor to public
jampukka Oct 3, 2017
05ddaff
Merge remote-tracking branch 'oskari/develop' into feature/timeseries…
jampukka Oct 3, 2017
7a344fe
bump version
jampukka Oct 3, 2017
4b2bd87
Merge remote-tracking branch 'oskari/develop' into feature/timeseries…
jampukka Nov 13, 2017
db0cf78
Fix merge
jampukka Nov 13, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add logic that checks that the response from service is a proper GetC…
…apabilities response

Remove recursion from loadCapabilitiesFromService

Let XMLStreamReader determine the charset in XML prolog

Remove encoding logic from GetGtWMSCapabilities by removing the prolog after decoding it succesfully
  • Loading branch information
jampukka committed Aug 25, 2017
commit 0ea589b8ab0ad953050188c6dc5298abc523d3f3
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ public void migrate(Connection connection) throws SQLException {
}

keys.add(layerKey);
String data = CapabilitiesCacheService.loadCapabilitiesFromService(layer, null);
String data = CapabilitiesCacheService.loadCapabilitiesFromService(layer);
if (data == null) {
LOG.warn("Failed to read capabilities from service!");
continue;
}
OskariLayerCapabilities draft = new OskariLayerCapabilities(
layer.getSimplifiedUrl(true),
layer.getType(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
package fi.nls.oskari.service.capabilities;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import fi.nls.oskari.domain.map.OskariLayer;
import fi.nls.oskari.log.LogFactory;
import fi.nls.oskari.log.Logger;
import fi.nls.oskari.service.OskariComponent;
import fi.nls.oskari.service.ServiceException;
import fi.nls.oskari.util.IOHelper;
import fi.nls.oskari.util.PropertyUtil;
import fi.nls.oskari.util.XmlHelper;

public abstract class CapabilitiesCacheService extends OskariComponent {

private static final Logger LOG = LogFactory.getLogger(CapabilitiesCacheService.class);
private static final String ENCODE_ATTRIBUTE = "encoding=\"";

private static final Map<String, String> TYPE_MAPPING = new HashMap<>(5);
static {
TYPE_MAPPING.put(OskariLayer.TYPE_WMS, "WMS");
Expand All @@ -37,6 +44,34 @@ public abstract class CapabilitiesCacheService extends OskariComponent {
protected abstract void updateMultiple(final List<OskariLayerCapabilities> capabilities);
protected abstract List<OskariLayerCapabilities> getAllOlderThan(final long maxAgeMs);

public void updateAllOlderThan(final long maxAgeMs) {
List<OskariLayerCapabilities> updates = new ArrayList<>();
for (OskariLayerCapabilities capabilities : getAllOlderThan(maxAgeMs)) {
String url = capabilities.getUrl();
String type = capabilities.getLayertype();
String version = capabilities.getVersion();
String dataOld = capabilities.getData();

OskariLayer layer = createTempOskariLayer(url, type, null, null, version);
String data = loadCapabilitiesFromService(layer);

if (data.isEmpty()) {
LOG.warn("Getting Capabilities from service failed for url:", url, "- skipping!");
continue;
}

if (dataOld.equals(data)) {
LOG.warn("New data is equal to old data for url:", url, "- skipping!");
continue;
}

capabilities.setData(data);
updates.add(capabilities);
}

updateMultiple(updates);
}

public OskariLayerCapabilities getCapabilities(String url, String type, String version)
throws ServiceException {
return getCapabilities(url, type, null, null, version);
Expand Down Expand Up @@ -69,11 +104,6 @@ public OskariLayerCapabilities getCapabilities(final OskariLayer layer)

public OskariLayerCapabilities getCapabilities(final OskariLayer layer, final boolean loadFromService)
throws ServiceException {
return getCapabilities(layer, null, loadFromService);
}

public OskariLayerCapabilities getCapabilities(final OskariLayer layer, String encoding, final boolean loadFromService)
throws ServiceException {
final String url = layer.getSimplifiedUrl(true);
final String type = layer.getType();
final String version = layer.getVersion();
Expand All @@ -87,124 +117,184 @@ public OskariLayerCapabilities getCapabilities(final OskariLayer layer, String e
}

// get xml from service
final String data = loadCapabilitiesFromService(layer, encoding, loadFromService);
final String data = loadCapabilitiesFromService(layer);
if (data == null || data.trim().isEmpty()) {
throw new ServiceException("Failed to load capabilities from service!");
}

try {
return save(new OskariLayerCapabilities(url, type, version, data));
} catch (IllegalArgumentException e) {
throw new ServiceException("Failed to save capabilities: " + e.getMessage());
}
}

public static String loadCapabilitiesFromService(OskariLayer layer, String encoding) {
return loadCapabilitiesFromService(layer, encoding, false);
}

private static String loadCapabilitiesFromService(OskariLayer layer, String encoding, final boolean norecursion) {
public static String loadCapabilitiesFromService(OskariLayer layer) {
final String url = contructCapabilitiesUrl(layer);
if (encoding == null) {
encoding = IOHelper.DEFAULT_CHARSET;
if (url.isEmpty()) {
return null;
}

String encoding = null;
byte[] data = null;
try {
final HttpURLConnection conn = IOHelper.getConnection(url, layer.getUsername(), layer.getPassword());
conn.setReadTimeout(TIMEOUT_MS);

final int sc = conn.getResponseCode();
if (sc != HttpURLConnection.HTTP_OK) {
LOG.warn("Unexpected Status code: ", sc, " url: ", url);
return "";
LOG.warn("Unexpected Status code:", sc, " url:", url);
return null;
}

final String contentType = conn.getContentType();
if (contentType != null && contentType.toLowerCase().indexOf("xml") == -1) {
// not xml based on contentType
LOG.warn("Unexpected Content-Type: ", contentType, " url: ", url);
return "";
LOG.warn("Unexpected Content-Type:", contentType, "url:", url);
return null;
}

final String response = IOHelper.readString(conn, encoding);
final String charset = getEncodingFromXml(response);

if (norecursion || charset == null || encoding.equalsIgnoreCase(charset)) {
return response;
}
return loadCapabilitiesFromService(layer, charset, true);
encoding = IOHelper.getCharset(conn);
data = IOHelper.readBytes(conn);
} catch (IOException e) {
LOG.warn(e, "IOException occured, url: ", url, " error message: ", e.getMessage());
return "";
LOG.warn(e, "IOException occured, url:", url);
return null;
}
}

public void updateAllOlderThan(final long maxAgeMs) {
List<OskariLayerCapabilities> updates = new ArrayList<>();
for (OskariLayerCapabilities capabilities : getAllOlderThan(maxAgeMs)) {
String url = capabilities.getUrl();
String type = capabilities.getLayertype();
String version = capabilities.getVersion();
String dataOld = capabilities.getData();

OskariLayer layer = createTempOskariLayer(url, type, null, null, version);
String data = loadCapabilitiesFromService(layer, IOHelper.DEFAULT_CHARSET);

if (data.isEmpty()) {
LOG.warn("Getting Capabilities from service failed for url:", url, "- skipping!");
continue;
try {
XMLInputFactory xif = XMLInputFactory.newFactory();
xif.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, true);
XMLStreamReader xsr = xif.createXMLStreamReader(new ByteArrayInputStream(data));

// Check XML prolog for character encoding
String xmlEncoding = xsr.getCharacterEncodingScheme();
if (xmlEncoding != null) {
if (encoding != null && !xmlEncoding.equalsIgnoreCase(encoding)) {
LOG.error("Content-Type header specified a different encoding than XML prolog!");
return null;
}
encoding = xmlEncoding;
}
if (encoding == null) {
LOG.debug("Charset wasn't set on either the Content-Type or the XML prolog"
+ "using UTF-8 as default value");
encoding = IOHelper.DEFAULT_CHARSET;
}

if (dataOld.equals(data)) {
LOG.warn("New data is equal to old data for url:", url, "- skipping!");
continue;
// Check that the response is what we expect
if (!checkCapabilities(xsr, layer.getType(), layer.getVersion())) {
return null;
}

capabilities.setData(data);
updates.add(capabilities);
// Convert "utf-8" to "UTF-8" for example
encoding = encoding.toUpperCase();
String xml = new String(data, encoding);
// Strip the potential prolog from XML so that we
// don't have to worry about the specified charset
return XmlHelper.stripPrologFromXML(xml);
} catch (FactoryConfigurationError | XMLStreamException e) {
LOG.warn(e, "Failed to parse XML from response");
} catch (UnsupportedEncodingException e) {
LOG.warn(e, "Failed to Encode byte[] to String encoding:", encoding);
}

updateMultiple(updates);
return null;
}

public static String contructCapabilitiesUrl(final OskariLayer layer) {
if (layer == null) {
return "";
}
final String url = layer.getSimplifiedUrl(true);

final String url = layer.getSimplifiedUrl(true);
final String urlLC = url.toLowerCase();
final String serviceType = TYPE_MAPPING.get(layer.getType());

final Map<String, String> params = new HashMap<String, String>();
// check existing params
if(!url.toLowerCase().contains("service=")) {
params.put("service", TYPE_MAPPING.get(layer.getType()));
if (!urlLC.contains("service=")) {
params.put("service", serviceType);
}
if(!url.toLowerCase().contains("request=")) {
if (!urlLC.contains("request=")) {
params.put("request", "GetCapabilities");
}
if(!url.toLowerCase().contains("version=") && layer.getVersion() != null) {
params.put("version", layer.getVersion());
if (!urlLC.contains("version=") && layer.getVersion() != null) {
params.put(getVersionNegotiationKey(serviceType), layer.getVersion());
}

return IOHelper.constructUrl(url, params);
}

// TODO: maybe use some lib instead?
public static String getEncodingFromXml(final String response) {
if(response == null) {
return null;
private static String getVersionNegotiationKey(String service) {
if (service != null) {
switch (service) {
case "WMS":
return "version";
case "WFS":
case "WMTS":
return "acceptVersions";
}
}
return "";
}

final String[] processingSplit = response.split("\\?>");
private static boolean checkCapabilities(XMLStreamReader xsr, String type, String version) {
if (!advanceToRootElement(xsr)) {
// Could not advance to root element
return false;
}
String ns = xsr.getNamespaceURI();
String name = xsr.getLocalName();
return checkCapabilities(type, version, ns, name);
}

if (processingSplit == null || processingSplit.length == 0) {
return null;
private static boolean advanceToRootElement(XMLStreamReader xsr) {
try {
if (xsr.nextTag() != XMLStreamConstants.START_DOCUMENT) {
LOG.warn("Document did not start with a START_DOCUMENT!");
return false;
}
if (xsr.nextTag() != XMLStreamConstants.START_ELEMENT) {
LOG.warn("Could not find root element!");
return false;
}
return true;
} catch (XMLStreamException e) {
LOG.warn(e, "Failed to find root element!");
return false;
}
}

int encodeAttributeStart = processingSplit[0].indexOf(ENCODE_ATTRIBUTE);
if (encodeAttributeStart > 0) {
encodeAttributeStart = encodeAttributeStart + ENCODE_ATTRIBUTE.length();
return processingSplit[0].substring(encodeAttributeStart, processingSplit[0].indexOf('"', encodeAttributeStart));
private static boolean checkCapabilities(String type, String version, String ns, String name) {
LOG.debug("Checking capabilities, type:", type, "version:", version,
"namespace", ns, "root element", name);

switch (type) {
case OskariLayer.TYPE_WMS:
if (version == null) {
// Layer didn't specify a version - response could could be anything
return isWMS130Capabilities(ns, name) || isWMSLessThan130Capabilities(ns, name);
} else if ("1.3.0".equals(version)) {
return isWMS130Capabilities(ns, name);
} else {
return isWMSLessThan130Capabilities(ns, name);
}
case OskariLayer.TYPE_WFS:
return ns.startsWith("http://www.opengis.net/wfs/") && "WFS_Capabilities".equals(name);
case OskariLayer.TYPE_WMTS:
return ns.startsWith("http://www.opengis.net/wmts/") && "Capabilities".equals(name);
}
return false;
}

return null;
private static boolean isWMSLessThan130Capabilities(String ns, String name) {
return ns == null
&& "WMT_MS_Capabilities".equals(name);
}

private static boolean isWMS130Capabilities(String ns, String name) {
return ns != null
&& ns.startsWith("http://www.opengis.net/wms/")
&& "WMS_Capabilities".equals(name);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,14 @@ private GetGtWMSCapabilities() {
}

// based on https://github.com/geotools/geotools/blob/master/modules/extension/wms/src/test/java/org/geotools/data/wms/test/WMS1_0_0_OnlineTest.java#L253-L276
public static WMSCapabilities createCapabilities(String xml, String encoding) {
public static WMSCapabilities createCapabilities(String xml) {
if(xml == null || xml.isEmpty()) {
return null;
}
if (encoding == null) {
encoding = StandardCharsets.UTF_8.toString();
}
final Map hints = new HashMap();
hints.put(DocumentHandler.DEFAULT_NAMESPACE_HINT_KEY, WMSSchema.getInstance());
hints.put(DocumentFactory.VALIDATION_HINT, false);
try(InputStream stream = new ByteArrayInputStream(xml.getBytes(encoding))) {
try(InputStream stream = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) {
final Object object = DocumentFactory.getInstance(stream, hints, Level.WARNING);
if(object instanceof WMSCapabilities) {
return (WMSCapabilities) object;
Expand Down Expand Up @@ -85,11 +82,10 @@ public static JSONObject getWMSCapabilities(final String rurl, final String user
String capabilitiesXML = capabilities.getData();
if(capabilitiesXML == null || capabilitiesXML.trim().isEmpty()) {
// retry from service - might get empty xml from db
capabilities = service.getCapabilities(rurl, "wmslayer", user, pwd, version, true);
capabilities = service.getCapabilities(rurl, OskariLayer.TYPE_WMS, user, pwd, version, true);
capabilitiesXML = capabilities.getData();
}
String encoding = CapabilitiesCacheService.getEncodingFromXml(capabilitiesXML);
WMSCapabilities caps = createCapabilities(capabilitiesXML, encoding);
WMSCapabilities caps = createCapabilities(capabilitiesXML);
// caps to json
return parseLayer(caps.getLayer(), rurl, caps, capabilitiesXML, currentCrs, false);
} catch (Exception ex) {
Expand Down