access.deploy.geoserver.PiazzaEnvironment.java Source code

Java tutorial

Introduction

Here is the source code for access.deploy.geoserver.PiazzaEnvironment.java

Source

/**
 * Copyright 2016, RadiantBlue Technologies, Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **/
package access.deploy.geoserver;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;

import javax.annotation.PostConstruct;

import org.apache.commons.io.IOUtils;
import org.apache.http.client.HttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import com.fasterxml.jackson.databind.ObjectMapper;

import access.util.AccessUtilities;
import model.logger.AuditElement;
import model.logger.Severity;
import util.PiazzaLogger;

/**
 * Establishes a Piazza environment on the GeoServer during initialization to ensure that prerequisite Workspaces and
 * Data Stores exist on the server before use
 * 
 * @author Patrick.Doody
 *
 */
@Component
public class PiazzaEnvironment {
    @Value("${vcap.services.pz-postgres.credentials.db_host}")
    private String postgresHost;
    @Value("${vcap.services.pz-postgres.credentials.db_port}")
    private String postgresPort;
    @Value("${vcap.services.pz-postgres.credentials.db_name}")
    private String postgresDatabase;
    @Value("${vcap.services.pz-postgres.credentials.username}")
    private String postgresUser;
    @Value("${vcap.services.pz-postgres.credentials.password}")
    private String postgresPassword;
    @Value("${vcap.services.pz-postgres-service-key.credentials.username}")
    private String postgresServiceKeyUser;
    @Value("${vcap.services.pz-postgres-service-key.credentials.password}")
    private String postgresServiceKeyPassword;
    @Value("${geoserver.creation.timeout}")
    private int restTemplateConnectionReadTimeout;
    @Value("${exit.on.geoserver.provision.failure}")
    private Boolean exitOnGeoServerProvisionFailure;
    @Autowired
    private HttpClient httpClient;
    @Autowired
    private AuthHeaders authHeaders;
    @Autowired
    private PiazzaLogger pzLogger;
    @Autowired
    private AccessUtilities accessUtilities;

    // Class-scoped for mocks. We don't want to AutoWire.
    private RestTemplate restTemplate = new RestTemplate();

    private static final Logger LOGGER = LoggerFactory.getLogger(PiazzaEnvironment.class);
    private static final String ACCESS = "access";

    /**
     * Invokes initialization logic for Piazza workspace and PostGIS Data Store
     */
    @PostConstruct
    public void initializeEnvironment() {
        pzLogger.log("Initializing - checking GeoServer for required workspaces and data stores.",
                Severity.INFORMATIONAL);

        // Since we're on the startup thread, we want to try to complete quickly. i.e. don't wait for slow connections.
        // Configure a reasonable timeout for the rest client to abort slow requests.
        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(
                this.httpClient);
        requestFactory.setReadTimeout(restTemplateConnectionReadTimeout);
        restTemplate.setRequestFactory(requestFactory);

        // Check for Workspace
        try {
            String workspaceUri = String.format("%s/rest/workspaces/piazza.json",
                    accessUtilities.getGeoServerBaseUrl());
            if (!doesResourceExist(workspaceUri)) {
                createWorkspace();
            } else {
                LOGGER.info("GeoServer Piazza Workspace found.");
            }
        } catch (Exception exception) {
            String error = "Server error encountered while trying to check Piazza Workspace. Will not attempt to create this Resource.";
            LOGGER.warn(error, exception);
            pzLogger.log(error, Severity.WARNING);
        }

        // Check for Data Store
        try {
            String dataStoreUri = String.format("%s/rest/workspaces/piazza/datastores/piazza.json",
                    accessUtilities.getGeoServerBaseUrl());
            if (!doesResourceExist(dataStoreUri)) {
                createPostgresStore();
            } else {
                LOGGER.info("GeoServer Piazza Data Store found.");
            }
        } catch (Exception exception) {
            String error = "Server error encountered while trying to check Piazza Data Store. Will not attempt to create this Resource.";
            LOGGER.warn(error, exception);
            pzLogger.log(error, Severity.WARNING);
        }
    }

    /**
     * Checks if a GeoServer resource exists (200 OK returns from the server. 404 indicates not exists)
     * 
     * @return True if exists, false if not
     */
    private boolean doesResourceExist(String resourceUri) throws HttpStatusCodeException, RestClientException {
        // Check if exists
        HttpEntity<String> request = new HttpEntity<>(authHeaders.get());

        try {
            pzLogger.log(String.format("Checking if GeoServer Resource Exists at %s", resourceUri),
                    Severity.INFORMATIONAL, new AuditElement(ACCESS, "checkGeoServerResourceExists", resourceUri));
            ResponseEntity<String> response = restTemplate.exchange(resourceUri, HttpMethod.GET, request,
                    String.class);
            // Validate that the response body is JSON. Otherwise, an authentication redirect error may have occurred.
            ObjectMapper objectMapper = new ObjectMapper();
            try {
                objectMapper.readTree(response.getBody());
            } catch (IOException exception) {
                String error = String.format(
                        "Received a non-error response from GeoServer resource check for %s, but it was not valid JSON. Authentication may have failed.",
                        resourceUri);
                LOGGER.error(error, exception);
                pzLogger.log(error, Severity.ERROR);
                return false;
            }
            if (response.getStatusCode().is2xxSuccessful()) {
                return true;
            }
        } catch (HttpClientErrorException | HttpServerErrorException exception) {
            // If it's a 404, then it does not exist. Fall through.
            if (!exception.getStatusCode().equals(HttpStatus.NOT_FOUND)) {
                // If it's anything but a 404, then it's a server error and we should not proceed with creation. Throw
                // an exception.
                LOGGER.error("HTTP Status Error checking GeoServer Resource {} Exists : {}" + resourceUri,
                        exception.getStatusCode().toString(), exception);
                throw exception;
            }
        } catch (RestClientException exception) {
            LOGGER.error("Unexpected Error Checking GeoServer Resource Exists : " + resourceUri, exception);
            throw exception;
        }
        return false;
    }

    /**
     * Creates the Piazza workspace
     */
    private void createWorkspace() {
        // POST the Workspace
        authHeaders.setContentType(MediaType.APPLICATION_XML);
        String body = "<workspace><name>piazza</name></workspace>";
        HttpEntity<String> request = new HttpEntity<>(body, authHeaders.get());
        String uri = String.format("%s/rest/workspaces", accessUtilities.getGeoServerBaseUrl());
        try {
            pzLogger.log(String.format("Creating Piazza Workspace to %s", uri), Severity.INFORMATIONAL,
                    new AuditElement(ACCESS, "tryCreateGeoServerWorkspace", uri));
            restTemplate.exchange(uri, HttpMethod.POST, request, String.class);
            try {
                // Hack/Workaround: With certain versions of GeoServer, The workspace is not entirely created with this
                // POST request.
                // In order to properly have layers in this workspace succeed, we must first modify the workspace.
                // Perform
                // a PUT request that does nothing - but internally in GeoServer this fixes some configuration bug where
                // the
                // workspace would otherwise not perform properly.
                String updateWorkspaceUri = String.format("%s/rest/workspaces/piazza.xml",
                        accessUtilities.getGeoServerBaseUrl());
                restTemplate.exchange(updateWorkspaceUri, HttpMethod.PUT, request, String.class);
            } catch (HttpClientErrorException | HttpServerErrorException exception) {
                String error = String.format(
                        "Failed to execute a PUT request on the newly-created workspace: %s. This may indicate the workspace may not be fully configured for use.",
                        exception.getResponseBodyAsString());
                LOGGER.info(error, exception);
                pzLogger.log(error, Severity.WARNING);
            }
        } catch (HttpClientErrorException | HttpServerErrorException exception) {
            String error = String.format("HTTP Error occurred while trying to create Piazza Workspace: %s",
                    exception.getResponseBodyAsString());
            LOGGER.info(error, exception);
            pzLogger.log(error, Severity.ERROR);
            determineExit();
        } catch (Exception exception) {
            String error = String.format("Unexpected Error occurred while trying to create Piazza Workspace: %s",
                    exception.getMessage());
            LOGGER.error(error, exception);
            pzLogger.log(error, Severity.ERROR);
            determineExit();
        }
    }

    /**
     * Creates the Piazza Postgres vector data store
     */
    private void createPostgresStore() {
        // Get Request XML
        ClassLoader classLoader = getClass().getClassLoader();
        InputStream inputStream = null;
        String dataStoreBody = null;
        try {
            inputStream = classLoader.getResourceAsStream("templates" + File.separator + "createDataStore.xml");
            dataStoreBody = IOUtils.toString(inputStream);
        } catch (Exception exception) {
            LOGGER.error("Error reading GeoServer Data Store Template.", exception);
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (Exception exception) {
                LOGGER.error("Error closing GeoServer Data Store Template Stream.", exception);
            }
        }
        // Create Workspace
        if (dataStoreBody != null) {
            // Insert the credential data into the template
            dataStoreBody = dataStoreBody.replace("$DB_USER", postgresServiceKeyUser);
            dataStoreBody = dataStoreBody.replace("$DB_PASSWORD", postgresServiceKeyPassword);
            dataStoreBody = dataStoreBody.replace("$DB_PORT", postgresPort);
            dataStoreBody = dataStoreBody.replace("$DB_NAME", postgresDatabase);
            dataStoreBody = dataStoreBody.replace("$DB_HOST", postgresHost);

            // POST Data Store to GeoServer
            authHeaders.setContentType(MediaType.APPLICATION_XML);
            HttpEntity<String> request = new HttpEntity<>(dataStoreBody, authHeaders.get());
            String uri = String.format("%s/rest/workspaces/piazza/datastores",
                    accessUtilities.getGeoServerBaseUrl());
            try {
                pzLogger.log(String.format("Creating Piazza Data Store to %s", uri), Severity.INFORMATIONAL,
                        new AuditElement(ACCESS, "tryCreateGeoServerDataStore", uri));
                restTemplate.exchange(uri, HttpMethod.POST, request, String.class);
            } catch (HttpClientErrorException | HttpServerErrorException exception) {
                String error = String.format("HTTP Error occurred while trying to create Piazza Data Store: %s",
                        exception.getResponseBodyAsString());
                LOGGER.info(error, exception);
                pzLogger.log(error, Severity.WARNING);
                determineExit();
            } catch (Exception exception) {
                String error = String.format(
                        "Unexpected Error occurred while trying to create Piazza Data Store: %s",
                        exception.getMessage());
                LOGGER.error(error, exception);
                pzLogger.log(error, Severity.ERROR);
                determineExit();
            }
        } else {
            pzLogger.log("Could not create GeoServer Data Store. Could not load Request XML from local Resources.",
                    Severity.ERROR);
        }
    }

    /**
     * If the application is configured to exit on GeoServer configuration error, this method will terminate it.
     */
    private void determineExit() {
        if (exitOnGeoServerProvisionFailure.booleanValue()) {
            pzLogger.log(
                    "GeoServer resources could not be appropriately provisioned due to errors received from GeoServer. This application is configured to shut down and will do so now.",
                    Severity.INFORMATIONAL);
            System.exit(1);
        }
    }
}