package mil.nga.giat.geowave.service.impl; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.ServletConfig; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Entity; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import mil.nga.giat.geowave.adapter.vector.plugin.GeoWaveGTDataStoreFactory; import mil.nga.giat.geowave.adapter.vector.plugin.GeoWavePluginConfig; import mil.nga.giat.geowave.core.cli.operations.config.security.utils.SecurityUtils; import mil.nga.giat.geowave.core.cli.utils.URLUtils; import mil.nga.giat.geowave.service.GeoserverService; import mil.nga.giat.geowave.service.ServiceUtils; import net.sf.json.JSONArray; import net.sf.json.JSONObject; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature; import org.glassfish.jersey.media.multipart.FormDataBodyPart; import org.glassfish.jersey.media.multipart.FormDataMultiPart; @Produces(MediaType.APPLICATION_JSON) @Path("/geoserver") public class GeoserverServiceImpl implements GeoserverService { private final static Logger log = LoggerFactory.getLogger(GeoserverServiceImpl.class); private final static int defaultIndentation = 2; private String geoserverUrl; private final String geoserverUser; private String geoserverPass; private final String defaultWorkspace; public GeoserverServiceImpl( @Context final ServletConfig servletConfig ) { Properties props = null; String confPropFilename = servletConfig.getInitParameter("config.properties"); try (InputStream is = servletConfig.getServletContext().getResourceAsStream( confPropFilename)) { props = ServiceUtils.loadProperties(is); } catch (IOException e) { log.error( e.getLocalizedMessage(), e); } geoserverUrl = ServiceUtils.getProperty( props, "geoserver.url"); try { geoserverUrl = URLUtils.getUrl(geoserverUrl); } catch (MalformedURLException | URISyntaxException e) { log.error( "An error occurred validating url [" + e.getLocalizedMessage() + "]", e); } geoserverUser = ServiceUtils.getProperty( props, "geoserver.username"); geoserverPass = ServiceUtils.getProperty( props, "geoserver.password"); try { File resourceFile = SecurityUtils.getFormattedTokenKeyFileForConfig(new File( confPropFilename)); geoserverPass = SecurityUtils.decryptHexEncodedValue( geoserverPass, resourceFile.getAbsolutePath()); } catch (Exception e) { log.error( "An error occurred decrypting password: " + e.getLocalizedMessage(), e); } defaultWorkspace = ServiceUtils.getProperty( props, "geoserver.workspace"); } @Override @GET @Path("/workspaces") @Produces(MediaType.APPLICATION_JSON) public Response getWorkspaces() { final Client client = ClientBuilder.newClient().register( HttpAuthenticationFeature.basic( geoserverUser, geoserverPass)); final WebTarget target = client.target(geoserverUrl); final Response resp = target.path( "rest/workspaces.json").request().get(); if (resp.getStatus() == Status.OK.getStatusCode()) { resp.bufferEntity(); // get the workspace names final JSONArray workspaceArray = getArrayEntryNames( JSONObject.fromObject(resp.readEntity(String.class)), "workspaces", "workspace"); final JSONObject workspacesObj = new JSONObject(); workspacesObj.put( "workspaces", workspaceArray); return Response.ok( workspacesObj.toString(defaultIndentation)).build(); } return resp; } private JSONArray getArrayEntryNames( JSONObject jsonObj, final String firstKey, final String secondKey ) { // get the top level object/array if (jsonObj.get(firstKey) instanceof JSONObject) { jsonObj = jsonObj.getJSONObject(firstKey); } else if (jsonObj.get(firstKey) instanceof JSONArray) { final JSONArray tempArray = jsonObj.getJSONArray(firstKey); if (tempArray.size() > 0) { jsonObj = tempArray.getJSONObject(0); } } // get the sub level object/array final JSONArray entryArray = new JSONArray(); if (jsonObj.get(secondKey) instanceof JSONObject) { final JSONObject entry = new JSONObject(); entry.put( "name", jsonObj.getJSONObject( secondKey).getString( "name")); entryArray.add(entry); } else if (jsonObj.get(secondKey) instanceof JSONArray) { final JSONArray entries = jsonObj.getJSONArray(secondKey); for (int i = 0; i < entries.size(); i++) { final JSONObject entry = new JSONObject(); entry.put( "name", entries.getJSONObject( i).getString( "name")); entryArray.add(entry); } } return entryArray; } @Override @POST @Path("/workspaces") @Produces(MediaType.APPLICATION_JSON) public Response createWorkspace( final FormDataMultiPart multiPart ) { final String workspace = multiPart.getField( "workspace").getValue(); final Client client = ClientBuilder.newClient().register( HttpAuthenticationFeature.basic( geoserverUser, geoserverPass)); final WebTarget target = client.target(geoserverUrl); return target.path( "rest/workspaces").request().post( Entity.entity( "{'workspace':{'name':'" + workspace + "'}}", MediaType.APPLICATION_JSON)); } @Override @DELETE @Path("/workspaces/{workspace}") public Response deleteWorkspace( @PathParam("workspace") final String workspace ) { final Client client = ClientBuilder.newClient().register( HttpAuthenticationFeature.basic( geoserverUser, geoserverPass)); final WebTarget target = client.target(geoserverUrl); return target.path( "rest/workspaces/" + workspace).queryParam( "recurse", "true").request().delete(); } @Override @GET @Path("/styles") @Produces(MediaType.APPLICATION_JSON) public Response getStyles() { final Client client = ClientBuilder.newClient().register( HttpAuthenticationFeature.basic( geoserverUser, geoserverPass)); final WebTarget target = client.target(geoserverUrl); final Response resp = target.path( "rest/styles.json").request().get(); if (resp.getStatus() == Status.OK.getStatusCode()) { resp.bufferEntity(); // get the style names final JSONArray styleArray = getArrayEntryNames( JSONObject.fromObject(resp.readEntity(String.class)), "styles", "style"); final JSONObject stylesObj = new JSONObject(); stylesObj.put( "styles", styleArray); return Response.ok( stylesObj.toString(defaultIndentation)).build(); } return resp; } @Override @GET @Path("/styles/{styleName}") @Produces(MediaType.APPLICATION_OCTET_STREAM) public Response getStyle( @PathParam("styleName") final String styleName ) { final Client client = ClientBuilder.newClient().register( HttpAuthenticationFeature.basic( geoserverUser, geoserverPass)); final WebTarget target = client.target(geoserverUrl); final Response resp = target.path( "rest/styles/" + styleName + ".sld").request().get(); if (resp.getStatus() == Status.OK.getStatusCode()) { final InputStream inStream = (InputStream) resp.getEntity(); return Response.ok( inStream, MediaType.APPLICATION_XML).header( "Content-Disposition", "attachment; filename=\"" + styleName + ".sld\"").build(); } return resp; } @Override @POST @Path("/styles") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.MULTIPART_FORM_DATA) public Response publishStyle( final FormDataMultiPart multiPart ) { final Collection<FormDataBodyPart> fileFields = multiPart.getFields("file"); if (fileFields == null) { return Response.noContent().build(); } // read the list of files & upload to geoserver services for (final FormDataBodyPart field : fileFields) { final String filename = field.getFormDataContentDisposition().getFileName(); if (filename.endsWith(".sld") || filename.endsWith(".xml")) { final String styleName = filename.substring( 0, filename.length() - 4); final InputStream inStream = field.getValueAs(InputStream.class); final Client client = ClientBuilder.newClient().register( HttpAuthenticationFeature.basic( geoserverUser, geoserverPass)); final WebTarget target = client.target(geoserverUrl); // create a new geoserver style target.path( "rest/styles").request().post( Entity.entity( "{'style':{'name':'" + styleName + "','filename':'" + styleName + ".sld'}}", MediaType.APPLICATION_JSON)); // upload the style to geoserver final Response resp = target.path( "rest/styles/" + styleName).request().put( Entity.entity( inStream, "application/vnd.ogc.sld+xml")); if (resp.getStatus() != Status.OK.getStatusCode()) { return resp; } } } return Response.ok().build(); } @Override @DELETE @Path("/styles/{styleName}") public Response deleteStyle( @PathParam("styleName") final String styleName ) { final Client client = ClientBuilder.newClient().register( HttpAuthenticationFeature.basic( geoserverUser, geoserverPass)); final WebTarget target = client.target(geoserverUrl); return target.path( "rest/styles/" + styleName).request().delete(); } @Override @GET @Path("/datastores") @Produces(MediaType.APPLICATION_JSON) public Response getDatastores( @DefaultValue("") @QueryParam("workspace") String customWorkspace ) { customWorkspace = (customWorkspace.equals("")) ? defaultWorkspace : customWorkspace; final Client client = ClientBuilder.newClient().register( HttpAuthenticationFeature.basic( geoserverUser, geoserverPass)); final WebTarget target = client.target(geoserverUrl); final Response resp = target.path( "rest/workspaces/" + customWorkspace + "/datastores.json").request().get(); if (resp.getStatus() == Status.OK.getStatusCode()) { resp.bufferEntity(); // get the datastore names final JSONArray datastoreArray = getArrayEntryNames( JSONObject.fromObject(resp.readEntity(String.class)), "dataStores", "dataStore"); final JSONArray datastores = new JSONArray(); for (int i = 0; i < datastoreArray.size(); i++) { final String name = datastoreArray.getJSONObject( i).getString( "name"); final JSONObject dsObj = JSONObject.fromObject( getDatastore( name, customWorkspace).getEntity()).getJSONObject( "dataStore"); // only process the GeoWave datastores if ((dsObj != null) && dsObj.containsKey("type") && dsObj.getString( "type").startsWith( "GeoWave Datastore")) { final JSONObject datastore = new JSONObject(); datastore.put( "name", name); JSONArray entryArray = null; if (dsObj.get("connectionParameters") instanceof JSONObject) { entryArray = dsObj.getJSONObject( "connectionParameters").getJSONArray( "entry"); } else if (dsObj.get("connectionParameters") instanceof JSONArray) { entryArray = dsObj.getJSONArray( "connectionParameters").getJSONObject( 0).getJSONArray( "entry"); } if (entryArray == null) { log .error("entry Array was null; didn't find a valid connectionParameters datastore object of type JSONObject or JSONArray"); } else { // report connection params for each data store for (int j = 0; j < entryArray.size(); j++) { final JSONObject entry = entryArray.getJSONObject(j); final String key = entry.getString("@key"); final String value = entry.getString("$"); datastore.put( key, value); } } datastores.add(datastore); } } final JSONObject dsObj = new JSONObject(); dsObj.put( "dataStores", datastores); return Response.ok( dsObj.toString(defaultIndentation)).build(); } return resp; } @Override @GET @Path("/datastores/{datastoreName}") @Produces(MediaType.APPLICATION_JSON) public Response getDatastore( @PathParam("datastoreName") final String datastoreName, @DefaultValue("") @QueryParam("workspace") String customWorkspace ) { customWorkspace = (customWorkspace.equals("")) ? defaultWorkspace : customWorkspace; final Client client = ClientBuilder.newClient().register( HttpAuthenticationFeature.basic( geoserverUser, geoserverPass)); final WebTarget target = client.target(geoserverUrl); final Response resp = target.path( "rest/workspaces/" + customWorkspace + "/datastores/" + datastoreName + ".json").request().get(); if (resp.getStatus() == Status.OK.getStatusCode()) { JSONObject datastore = null; try { datastore = JSONObject.fromObject(IOUtils.toString((InputStream) resp.getEntity())); } catch (final IOException e) { log.error( "Unable to parse datastore.", e); } if (datastore != null) { return Response.ok( datastore.toString(defaultIndentation)).build(); } } return resp; } @Override @POST @Path("/datastores") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.MULTIPART_FORM_DATA) public Response publishDatastore( final FormDataMultiPart multiPart ) { final Map<String, List<FormDataBodyPart>> fieldMap = multiPart.getFields(); String lockMgmt = "memory"; String authMgmtPrvdr = "empty"; String authDataUrl = ""; String customWorkspace = defaultWorkspace; String queryIndexStrategy = "Best Match"; String geowaveStoreType = "memory"; String name = "geowave"; final Map<String, String> geowaveStoreConfig = new HashMap<String, String>(); for (final Entry<String, List<FormDataBodyPart>> e : fieldMap.entrySet()) { if ((e.getValue() != null) && !e.getValue().isEmpty()) { if (e.getKey().equals( "lockMgmt")) { lockMgmt = e.getValue().get( 0).getValue(); } else if (e.getKey().equals( "queryIndexStrategy")) { queryIndexStrategy = e.getValue().get( 0).getValue(); } else if (e.getKey().equals( "authMgmtPrvdr")) { authMgmtPrvdr = e.getValue().get( 0).getValue(); } else if (e.getKey().equals( "authDataUrl")) { authDataUrl = e.getValue().get( 0).getValue(); } else if (e.getKey().equals( "workspace")) { customWorkspace = e.getValue().get( 0).getValue(); } else if (e.getKey().equals( "name")) { name = e.getValue().get( 0).getValue(); } else if (e.getKey().equals( "geowaveStoreType")) { geowaveStoreType = e.getValue().get( 0).getValue(); } else { geowaveStoreConfig.put( e.getKey(), e.getValue().get( 0).getValue()); } } } final Client client = ClientBuilder.newClient().register( HttpAuthenticationFeature.basic( geoserverUser, geoserverPass)); final WebTarget target = client.target(geoserverUrl); final String dataStoreJson = createDatastoreJson( geowaveStoreType, geowaveStoreConfig, name, lockMgmt, authMgmtPrvdr, authDataUrl, queryIndexStrategy, true); // create a new geoserver style final Response resp = target.path( "rest/workspaces/" + customWorkspace + "/datastores").request().post( Entity.entity( dataStoreJson, MediaType.APPLICATION_JSON)); if (resp.getStatus() == Status.CREATED.getStatusCode()) { return Response.ok().build(); } return resp; } @Override @DELETE @Path("/datastores/{datastoreName}") public Response deleteDatastore( @PathParam("datastoreName") final String datastoreName, @DefaultValue("") @QueryParam("workspace") String customWorkspace ) { customWorkspace = (customWorkspace.equals("")) ? defaultWorkspace : customWorkspace; final Client client = ClientBuilder.newClient().register( HttpAuthenticationFeature.basic( geoserverUser, geoserverPass)); final WebTarget target = client.target(geoserverUrl); return target.path( "rest/workspaces/" + customWorkspace + "/datastores/" + datastoreName).queryParam( "recurse", "true").request().delete(); } private String createDatastoreJson( final String geowaveStoreType, final Map<String, String> geowaveStoreConfig, final String name, final String lockMgmt, final String authMgmtProvider, final String authDataUrl, final String queryIndexStrategy, final boolean enabled ) { final JSONObject dataStore = new JSONObject(); dataStore.put( "name", name); dataStore.put( "type", GeoWaveGTDataStoreFactory.DISPLAY_NAME_PREFIX + geowaveStoreType); dataStore.put( "enabled", Boolean.toString(enabled)); final JSONObject connParams = new JSONObject(); for (final Entry<String, String> e : geowaveStoreConfig.entrySet()) { connParams.put( e.getKey(), e.getValue()); } connParams.put( "Lock Management", lockMgmt); connParams.put( GeoWavePluginConfig.QUERY_INDEX_STRATEGY_KEY, queryIndexStrategy); connParams.put( "Authorization Management Provider", authMgmtProvider); if (!authMgmtProvider.equals("empty")) { connParams.put( "Authorization Data URL", authDataUrl); } dataStore.put( "connectionParameters", connParams); final JSONObject jsonObj = new JSONObject(); jsonObj.put( "dataStore", dataStore); return jsonObj.toString(); } @Override @GET @Path("/layers") @Produces(MediaType.APPLICATION_JSON) public Response getLayers() { final Client client = ClientBuilder.newClient().register( HttpAuthenticationFeature.basic( geoserverUser, geoserverPass)); final WebTarget target = client.target(geoserverUrl); final Response resp = target.path( "rest/layers.json").request().get(); if (resp.getStatus() == Status.OK.getStatusCode()) { resp.bufferEntity(); // get the datastore names final JSONArray layerArray = getArrayEntryNames( JSONObject.fromObject(resp.readEntity(String.class)), "layers", "layer"); final Map<String, List<String>> namespaceLayersMap = new HashMap<String, List<String>>(); final Pattern p = Pattern.compile("workspaces/(.*?)/datastores/(.*?)/"); for (int i = 0; i < layerArray.size(); i++) { final String name = layerArray.getJSONObject( i).getString( "name"); final String layer = (String) getLayer( name).getEntity(); // get the workspace and name for each datastore String workspace = null; String datastoreName = null; final Matcher m = p.matcher(layer); if (m.find()) { workspace = m.group(1); datastoreName = m.group(2); } if ((datastoreName != null) && (workspace != null)) { final JSONObject datastore = JSONObject.fromObject( getDatastore( datastoreName, workspace).getEntity()).getJSONObject( "dataStore"); // only process GeoWave layers if ((datastore != null) && datastore.containsKey("type") && datastore.getString( "type").startsWith( "GeoWave Datastore")) { JSONArray entryArray = null; if (datastore.get("connectionParameters") instanceof JSONObject) { entryArray = datastore.getJSONObject( "connectionParameters").getJSONArray( "entry"); } else if (datastore.get("connectionParameters") instanceof JSONArray) { entryArray = datastore.getJSONArray( "connectionParameters").getJSONObject( 0).getJSONArray( "entry"); } if (entryArray == null) { log .error("entry Array is null - didn't find a connectionParameters datastore object that was a JSONObject or JSONArray"); } else { // group layers by namespace for (int j = 0; j < entryArray.size(); j++) { final JSONObject entry = entryArray.getJSONObject(j); final String key = entry.getString("@key"); final String value = entry.getString("$"); if (key.startsWith(GeoWavePluginConfig.GEOWAVE_NAMESPACE_KEY)) { if (namespaceLayersMap.containsKey(value)) { namespaceLayersMap.get( value).add( name); } else { final ArrayList<String> layers = new ArrayList<String>(); layers.add(name); namespaceLayersMap.put( value, layers); } break; } } } } } } // create the json object with layers sorted by namespace final JSONArray layersArray = new JSONArray(); for (final Map.Entry<String, List<String>> kvp : namespaceLayersMap.entrySet()) { final JSONArray layers = new JSONArray(); for (int i = 0; i < kvp.getValue().size(); i++) { final JSONObject layerObj = new JSONObject(); layerObj.put( "name", kvp.getValue().get( i)); layers.add(layerObj); } final JSONObject layersObj = new JSONObject(); layersObj.put( "namespace", kvp.getKey()); layersObj.put( "layers", layers); layersArray.add(layersObj); } final JSONObject layersObj = new JSONObject(); layersObj.put( "layers", layersArray); return Response.ok( layersObj.toString(defaultIndentation)).build(); } return resp; } @Override @GET @Path("/layers/{layerName}") @Produces(MediaType.APPLICATION_JSON) public Response getLayer( @PathParam("layerName") final String layerName ) { final Client client = ClientBuilder.newClient().register( HttpAuthenticationFeature.basic( geoserverUser, geoserverPass)); final WebTarget target = client.target(geoserverUrl); final Response resp = target.path( "rest/layers/" + layerName + ".json").request().get(); if (resp.getStatus() == Status.OK.getStatusCode()) { JSONObject layer = null; try { layer = JSONObject.fromObject(IOUtils.toString((InputStream) resp.getEntity())); } catch (final IOException e) { log.error( "Unable to parse layer.", e); } if (layer != null) { return Response.ok( layer.toString(defaultIndentation)).build(); } } return resp; } @Override @POST @Path("/layers") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.MULTIPART_FORM_DATA) public Response publishLayer( final FormDataMultiPart multiPart ) { final String datastore = multiPart.getField( "datastore").getValue(); final String defaultStyle = multiPart.getField( "defaultStyle").getValue(); final String customWorkspace = (multiPart.getField("workspace") != null) ? multiPart.getField( "workspace").getValue() : defaultWorkspace; String jsonString; try { jsonString = IOUtils.toString(multiPart.getField( "featureType").getValueAs( InputStream.class)); } catch (final IOException e) { throw new WebApplicationException( Response.status( Status.BAD_REQUEST).entity( "Layer Creation Failed - Unable to parse featureType").build()); } final String layerName = JSONObject.fromObject( jsonString).getJSONObject( "featureType").getString( "name"); final Client client = ClientBuilder.newClient().register( HttpAuthenticationFeature.basic( geoserverUser, geoserverPass)); final WebTarget target = client.target(geoserverUrl); Response resp = target.path( "rest/workspaces/" + customWorkspace + "/datastores/" + datastore + "/featuretypes").request().post( Entity.entity( jsonString, MediaType.APPLICATION_JSON)); if (resp.getStatus() != Status.CREATED.getStatusCode()) { return resp; } resp = target.path( "rest/layers/" + layerName).request().put( Entity.entity( "{'layer':{'defaultStyle':{'name':'" + defaultStyle + "'}}}", MediaType.APPLICATION_JSON)); return resp; } @Override @DELETE @Path("/layers/{layer}") public Response deleteLayer( @PathParam("layer") final String layerName ) { final Client client = ClientBuilder.newClient().register( HttpAuthenticationFeature.basic( geoserverUser, geoserverPass)); final WebTarget target = client.target(geoserverUrl); return target.path( "rest/layers/" + layerName).request().delete(); } }