/* (c) 2017 Open Source Geospatial Foundation - all rights reserved * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.opensearch.eo.response; import static org.geoserver.ows.util.ResponseUtils.appendQueryString; import static org.geoserver.ows.util.ResponseUtils.buildURL; import static org.geoserver.ows.util.ResponseUtils.params; import java.util.Collections; import java.util.Date; import java.util.LinkedHashMap; import java.util.Map; import java.util.stream.Collectors; import org.geoserver.config.GeoServerInfo; import org.geoserver.opensearch.eo.OSEODescription; import org.geoserver.opensearch.eo.OSEOInfo; import org.geoserver.opensearch.eo.OpenSearchParameters; import org.geoserver.opensearch.eo.store.OpenSearchAccess; import org.geoserver.ows.URLMangler.URLType; import org.geoserver.ows.util.ResponseUtils; import org.geotools.data.Parameter; import org.geotools.xml.transform.Translator; import org.xml.sax.ContentHandler; /** * Encodes a {@link DescriptionResponse} into a OSDD document * * @author Andrea Aime - GeoSolutions */ public class DescriptionTransformer extends LambdaTransformerBase { @Override public Translator createTranslator(ContentHandler handler) { return new OSEODescriptionTranslator(handler); } private class OSEODescriptionTranslator extends LambdaTranslatorSupport { public OSEODescriptionTranslator(ContentHandler contentHandler) { super(contentHandler); } @Override public void encode(Object o) throws IllegalArgumentException { OSEODescription description = (OSEODescription) o; Map<String, String> namespaces = new LinkedHashMap<>(); namespaces.put("xmlns", "http://a9.com/-/spec/opensearch/1.1/"); namespaces.put("xmlns:param", "http://a9.com/-/spec/opensearch/extensions/parameters/1.0/"); namespaces.put("xmlns:geo", "http://a9.com/-/opensearch/extensions/geo/1.0/"); namespaces.put("xmlns:time", "http://a9.com/-/opensearch/extensions/time/1.0/"); namespaces.put("xmlns:eo", "http://a9.com/-/opensearch/extensions/eo/1.0/"); for (OpenSearchAccess.ProductClass pc : OpenSearchAccess.ProductClass.values()) { namespaces.put("xmlns:" + pc.getPrefix(), pc.getNamespace()); } element("OpenSearchDescription", () -> describeOpenSearch(description), attributes(namespaces)); } private void describeOpenSearch(OSEODescription description) { OSEOInfo oseo = description.getServiceInfo(); // while the OpenSearch specification does not seem to mandate a specific order for tags, // the one of the spec examples has been followed in order to ensure maximum compatibility with clients element("ShortName", oseo.getName()); element("Description", oseo.getAbstract()); GeoServerInfo gs = description.getGeoserverInfo(); element("Contact", gs.getSettings().getContact().getContactEmail()); String tags = oseo.getKeywords().stream().map(k -> k.getValue()) .collect(Collectors.joining(" ")); element("Tags", tags); element("Url", NO_CONTENTS, attributes("rel", "self", // "template", buildSelfUrl(description), // "type", "application/opensearchdescription+xml")); element("Url", () -> describeParameters(description), attributes( // "rel", "results", // "template", buildResultsUrl(description, "atom"), // "type", "application/atom+xml")); element("LongName", oseo.getTitle()); element("Developer", oseo.getMaintainer()); element("SyndicationRight", "open"); // make configurable? element("AdultContent", "false"); element("Language", "en-us"); element("OutputEncoding", "UTF-8"); element("InputEncoding", "UTF-8"); } private String buildSelfUrl(OSEODescription description) { String baseURL = description.getBaseURL(); Map<String, String> params = buildParentIdParams(description); return buildURL(baseURL, "oseo/description", params, URLType.SERVICE); } private Map<String, String> buildParentIdParams(OSEODescription description) { Map<String, String> params; if (description.getParentId() == null) { params = Collections.emptyMap(); } else { params = params("parentId", description.getParentId()); } return params; } public String buildResultsUrl(OSEODescription description, String format) { String baseURL = description.getBaseURL(); Map<String, String> params = buildParentIdParams(description); String base = buildURL(baseURL, "oseo/search", params, URLType.SERVICE); // the template must not be url encoded instead String paramSpec = description.getSearchParameters().stream().map(p -> { String spec = p.key + "={"; spec += OpenSearchParameters.getQualifiedParamName(p, false); if (!p.required) { spec += "?"; } spec += "}"; return spec; }).collect(Collectors.joining("&")); return appendQueryString(base, paramSpec + "&httpAccept=" + ResponseUtils.urlEncode(format)); } private void describeParameters(OSEODescription description) { for (Parameter param : description.getSearchParameters()) { Runnable contentsEncoder = null; final Map<String, String> map = new LinkedHashMap<>(); map.put("name", param.key); map.put("value", "{" + OpenSearchParameters.getQualifiedParamName(param, false) + "}"); if (!param.isRequired()) { map.put("minimum", "0"); } if (param.metadata != null) { String[] keys = new String[] { OpenSearchParameters.MIN_INCLUSIVE, OpenSearchParameters.MAX_INCLUSIVE }; for (String key : keys) { Object value = param.metadata.get(key); if (value != null) { map.put(key, String.valueOf(value)); } } } if (!map.containsKey("pattern")) { Class type = param.getType(); if (Integer.class == type) { map.put("pattern", "[+-][0-9]+"); } else if (Float.class == type || Double.class == type) { map.put("pattern", "[-+]?[0-9]*\\.?[0-9]+"); } else if (Date.class.isAssignableFrom(type)) { map.put("pattern", "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]+)?(Z|[\\+\\-][0-9]{2}:[0-9]{2})$"); } } element("param:Parameter", contentsEncoder, attributes(map)); } } } }