eu.esdihumboldt.hale.io.geoserver.rest.AbstractResourceManager.java Source code

Java tutorial

Introduction

Here is the source code for eu.esdihumboldt.hale.io.geoserver.rest.AbstractResourceManager.java

Source

/*
 * Copyright (c) 2015 Data Harmonisation Panel
 * 
 * All rights reserved. This program and the accompanying materials are made
 * available under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the License,
 * or (at your option) any later version.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution. If not, see <http://www.gnu.org/licenses/>.
 * 
 * Contributors:
 *     Data Harmonisation Panel <http://www.dhpanel.eu>
 */

package eu.esdihumboldt.hale.io.geoserver.rest;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.lang.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.fluent.Executor;
import org.apache.http.client.fluent.Request;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

import com.google.common.base.Joiner;

import eu.esdihumboldt.hale.io.geoserver.Resource;

/**
 * Base class for classes representing GeoServer resource managers.
 * 
 * <p>
 * The basic idea is that a resource manager can retrieve the list of resources
 * of type <code>T</code> and can execute the standard CRUD operations on a
 * specific resource instance, called the "managed resource", which must be
 * explicitly set by calling the {@link #setResource(Resource)} method.
 * </p>
 * 
 * @author Stefano Costa, GeoSolutions
 * @param <T> the type of the managed resource
 */
public abstract class AbstractResourceManager<T extends Resource> implements ResourceManager<T> {

    /**
     * Base path for all REST services.
     */
    public static final String REST_BASE = "rest";
    /**
     * Default request / response body format.
     */
    public static final String DEF_FORMAT = "xml";

    /**
     * Base GeoServer URL (e.g. http://localhost:8080/geoserver)
     */
    protected URL geoserverUrl;
    /**
     * The resource to manage.
     */
    protected T resource;

    private final Executor executor;

    /**
     * Constructor.
     * 
     * @param geoserverUrl the base GeoServer URL
     * @throws MalformedURLException if the provided URL is invalid
     */
    public AbstractResourceManager(String geoserverUrl) throws MalformedURLException {
        this(new URL(geoserverUrl));
    }

    /**
     * Constructor.
     * 
     * @param geoserverUrl the base GeoServer URL
     */
    public AbstractResourceManager(URL geoserverUrl) {
        if (geoserverUrl == null || geoserverUrl.getQuery() != null) {
            throw new IllegalArgumentException(
                    "GeoServer base URL must not be null and must not contain a query part");
        }
        this.geoserverUrl = geoserverUrl;
        this.executor = Executor.newInstance();
    }

    /**
     * @see eu.esdihumboldt.hale.io.geoserver.rest.ResourceManager#setCredentials(java.lang.String,
     *      java.lang.String)
     */
    @Override
    public void setCredentials(String user, String password) {
        HttpHost geoserverHost = new HttpHost(geoserverUrl.getHost(), geoserverUrl.getPort(),
                geoserverUrl.getProtocol());
        executor.auth(geoserverHost, user, password);
    }

    /**
     * @see eu.esdihumboldt.hale.io.geoserver.rest.ResourceManager#list()
     */
    @Override
    public Document list() {
        try {
            return executor.execute(Request.Get(getResourceListURL())).handleResponse(new XmlResponseHandler());
        } catch (Exception e) {
            throw new ResourceException(e);
        }
    }

    /**
     * @see eu.esdihumboldt.hale.io.geoserver.rest.ResourceManager#setResource(eu.esdihumboldt.hale.io.geoserver.Resource)
     */
    @Override
    public void setResource(T resource) {
        this.resource = resource;
    }

    /**
     * @see eu.esdihumboldt.hale.io.geoserver.rest.ResourceManager#exists()
     */
    @Override
    public boolean exists() {
        checkResourceSet();

        try {
            return executor.execute(Request.Get(getResourceURL())).handleResponse(new ResponseHandler<Boolean>() {

                /**
                 * @see org.apache.http.client.ResponseHandler#handleResponse(org.apache.http.HttpResponse)
                 */
                @Override
                public Boolean handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
                    int statusCode = response.getStatusLine().getStatusCode();
                    String reason = response.getStatusLine().getReasonPhrase();

                    switch (statusCode) {
                    case 200:
                        return true;
                    case 404:
                        return false;
                    default:
                        throw new HttpResponseException(statusCode, reason);
                    }
                }

            });
        } catch (Exception e) {
            throw new ResourceException(e);
        }
    }

    /**
     * @see eu.esdihumboldt.hale.io.geoserver.rest.ResourceManager#read()
     */
    @Override
    public Document read() {
        return read(null);
    }

    /**
     * @see eu.esdihumboldt.hale.io.geoserver.rest.ResourceManager#read(java.util.Map)
     */
    @Override
    public Document read(Map<String, String> parameters) {
        checkResourceSet();

        try {
            URI requestUri = buildRequestUri(getResourceURL(), parameters);
            return executor.execute(Request.Get(requestUri)).handleResponse(new XmlResponseHandler());
        } catch (Exception e) {
            throw new ResourceException(e);
        }
    }

    /**
     * @see eu.esdihumboldt.hale.io.geoserver.rest.ResourceManager#create()
     */
    @Override
    public URL create() {
        return create(null);
    }

    /**
     * @see eu.esdihumboldt.hale.io.geoserver.rest.ResourceManager#create(java.util.Map)
     */
    @Override
    public URL create(Map<String, String> parameters) {
        checkResourceSet();

        try {
            URI requestUri = buildRequestUri(getResourceListURL(), parameters);

            ByteArrayEntity entity = new ByteArrayEntity(resource.asByteArray());
            entity.setContentType(resource.contentType().getMimeType());

            return executor.execute(Request.Post(requestUri).body(entity))
                    .handleResponse(new ResponseHandler<URL>() {

                        /**
                         * @see org.apache.http.client.ResponseHandler#handleResponse(org.apache.http.HttpResponse)
                         */
                        @Override
                        public URL handleResponse(HttpResponse response)
                                throws ClientProtocolException, IOException {
                            StatusLine statusLine = response.getStatusLine();
                            if (statusLine.getStatusCode() >= 300) {
                                throw new HttpResponseException(statusLine.getStatusCode(),
                                        statusLine.getReasonPhrase());
                            }
                            if (statusLine.getStatusCode() == 201) {
                                Header locationHeader = response.getFirstHeader("Location");
                                if (locationHeader != null) {
                                    return new URL(locationHeader.getValue());
                                }
                            }
                            return null;
                        }
                    });
        } catch (Exception e) {
            throw new ResourceException(e);
        }
    }

    /**
     * @see eu.esdihumboldt.hale.io.geoserver.rest.ResourceManager#update()
     */
    @Override
    public void update() {
        update(null);
    }

    /**
     * @see eu.esdihumboldt.hale.io.geoserver.rest.ResourceManager#update(java.util.Map)
     */
    @Override
    public void update(Map<String, String> parameters) {
        checkResourceSet();

        try {
            URI requestUri = buildRequestUri(getResourceURL(), parameters);

            ByteArrayEntity entity = new ByteArrayEntity(resource.asByteArray());
            entity.setContentType(resource.contentType().getMimeType());

            executor.execute(Request.Put(requestUri).body(entity)).handleResponse(new EmptyResponseHandler());
        } catch (Exception e) {
            throw new ResourceException(e);
        }
    }

    /**
     * @see eu.esdihumboldt.hale.io.geoserver.rest.ResourceManager#delete()
     */
    @Override
    public void delete() {
        delete(null);
    }

    /**
     * @see eu.esdihumboldt.hale.io.geoserver.rest.ResourceManager#delete(java.util.Map)
     */
    @Override
    public void delete(Map<String, String> parameters) {
        checkResourceSet();

        try {
            URI requestUri = buildRequestUri(getResourceURL(), parameters);
            executor.execute(Request.Delete(requestUri)).handleResponse(new EmptyResponseHandler());
        } catch (Exception e) {
            throw new ResourceException(e);
        }
    }

    private void checkResourceSet() {
        if (this.resource == null) {
            throw new IllegalStateException("Resource not set");
        }
    }

    private URI buildRequestUri(String url, Map<String, String> parameters) throws URISyntaxException {

        URIBuilder uriBuilder = new URIBuilder(url);
        if (parameters != null) {
            parameters.forEach((param, value) -> uriBuilder.addParameter(param, value));
        }

        return uriBuilder.build();
    }

    /**
     * Construct URL of the list of resources of type <code>T</code>.
     * 
     * @return the resource list URL
     */
    protected String getResourceListURL() {
        return getRestServiceUrl(getResourceListPath());
    }

    /**
     * Construct URL of the managed resource.
     * 
     * @return the resource URL
     */
    protected String getResourceURL() {
        return getRestServiceUrl(getResourcePath());
    }

    private String getRestServiceUrl(String resourcePath) {
        List<String> urlParts = new ArrayList<String>();
        urlParts.add(geoserverUrl.toString());
        urlParts.add(REST_BASE);
        urlParts.add(resourcePath);

        urlParts.replaceAll(urlPart -> normalizeUrlPart(urlPart));
        final String resourceUrl = Joiner.on('/').skipNulls().join(urlParts);
        return Joiner.on(".").skipNulls().join(Arrays.asList(resourceUrl, getFormat()));
    }

    private String normalizeUrlPart(String urlPart) {
        // remove slashes at the beginning and end of the URL part
        // and return null if it empty or contains only whitespace
        urlPart = StringUtils.stripStart(urlPart, "/");
        urlPart = StringUtils.stripEnd(urlPart, "/");
        return StringUtils.defaultIfBlank(urlPart, null);
    }

    /**
     * Template method, to be implemented by subclasses.
     * 
     * <p>
     * Should return the path to the list of resources of type <code>T</code>
     * (relative to GeoServer's REST base path, e.g.
     * http://localhost:8080/geoserver/rest).
     * 
     * @return the resource list path
     */
    protected abstract String getResourceListPath();

    /**
     * Template method, to be implemented by subclasses.
     * 
     * <p>
     * Should return the path to the managed resource (relative to GeoServer's
     * REST base path, e.g. http://localhost:8080/geoserver/rest).
     * </p>
     * 
     * @return the resource path
     */
    protected abstract String getResourcePath();

    /**
     * Retrieves the default request / response body format.
     * 
     * @return the format
     */
    protected String getFormat() {
        return DEF_FORMAT;
    }

    /**
     * Response handler that parses the response body into an XML
     * {@link Document}.
     * 
     * @author Stefano Costa, GeoSolutions
     */
    private class XmlResponseHandler implements ResponseHandler<Document> {

        /**
         * @see org.apache.http.client.ResponseHandler#handleResponse(org.apache.http.HttpResponse)
         */
        @Override
        public Document handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
            StatusLine statusLine = response.getStatusLine();
            HttpEntity entity = response.getEntity();
            if (statusLine.getStatusCode() >= 300) {
                throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase());
            }
            if (entity == null) {
                throw new ClientProtocolException("Response contains no content");
            }
            DocumentBuilderFactory dbFac = DocumentBuilderFactory.newInstance();
            try {
                DocumentBuilder docBuilder = dbFac.newDocumentBuilder();
                ContentType contentType = ContentType.getOrDefault(entity);
                if (!isXml(contentType)) {
                    throw new ClientProtocolException("Unexpected content type: " + contentType);
                }
                Charset charset = contentType.getCharset();
                if (charset == null) {
                    charset = Charset.forName("UTF-8");
                }
                return docBuilder.parse(entity.getContent());
            } catch (ParserConfigurationException ex) {
                throw new IllegalStateException(ex);
            } catch (SAXException ex) {
                throw new ClientProtocolException("Malformed XML document", ex);
            }
        }

    }

    private boolean isXml(ContentType contentType) {
        if (contentType == null) {
            return false;
        }

        String mimeType = contentType.getMimeType();
        return mimeType.equals("text/xml") || mimeType.equals("application/xml");
    }

    /**
     * Response handler that does nothing with the response body, but just
     * checks the response status and throws an exception if the status code is
     * greater than or equal to 300.
     * 
     * @author Stefano Costa, GeoSolutions
     */
    private class EmptyResponseHandler implements ResponseHandler<Void> {

        /**
         * @see org.apache.http.client.ResponseHandler#handleResponse(org.apache.http.HttpResponse)
         */
        @Override
        public Void handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
            StatusLine statusLine = response.getStatusLine();
            if (statusLine.getStatusCode() >= 300) {
                throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase());
            }
            return null;
        }

    }
}