@@ -91,6 +94,9 @@ public class GeoServerRESTPublisher {
private final GeoServerRESTStyleManager styleManager;
+
+ private final GeoServerRESTImporterManager importerManager;
+
/**
* Creates a GeoServerRESTPublisher to connect against a GeoServer instance with the given URL and user credentials.
*
@@ -110,6 +116,7 @@ public class GeoServerRESTPublisher {
LOGGER.error("Bad URL: Calls to GeoServer are going to fail" , ex);
}
styleManager = new GeoServerRESTStyleManager(url, username, password);
+ importerManager = new GeoServerRESTImporterManager(url, username, password);
}
// ==========================================================================
@@ -3145,4 +3152,58 @@ public class GeoServerRESTPublisher {
}
+ /**
+ * Refers to {@link it.geosolutions.geoserver.rest.manager.GeoServerRESTImporterManager#postNewImport() postNewImport} method
+ *
+ * @throws Exception
+ */
+ public int postNewImport() throws Exception {
+ return importerManager.postNewImport();
+ }
+
+ /**
+ * Refers to {@link it.geosolutions.geoserver.rest.manager.GeoServerRESTImporterManager#postNewTaskAsMultiPartForm(int, String) postNewTaskAsMultiPartForm} method
+ *
+ * @throws Exception
+ */
+ public int postNewTaskAsMultiPartForm(int i, String data) throws Exception {
+ return importerManager.postNewTaskAsMultiPartForm(i, data);
+ }
+
+ /**
+ * Refers to {@link it.geosolutions.geoserver.rest.manager.GeoServerRESTImporterManager#getTask(int, int) getTask} method
+ *
+ * @throws Exception
+ */
+ public JSONObject getTask(int i, int t) throws Exception {
+ return importerManager.getTask(i, t);
+ }
+
+ /**
+ * Refers to {@link it.geosolutions.geoserver.rest.manager.GeoServerRESTImporterManager#putTask(int, int, String) putTask} method
+ *
+ * @throws Exception
+ */
+ public void putTask(int i, int t, String json) throws Exception {
+ importerManager.putTask(i, t, json);
+ }
+
+ /**
+ * Refers to {@link it.geosolutions.geoserver.rest.manager.GeoServerRESTImporterManager#putTaskLayer(int, int, String) putTaskLayer} method
+ *
+ * @throws Exception
+ */
+ public void putTaskLayer(int i, int t, String json) throws Exception {
+ importerManager.putTaskLayer(i, t, json);
+ }
+
+ /**
+ * Refers to {@link it.geosolutions.geoserver.rest.manager.GeoServerRESTImporterManager#postImport(int) postImport} method
+ *
+ * @throws Exception
+ */
+ public void postImport(int i) throws Exception {
+ importerManager.postImport(i);
+ }
+
}
diff --git a/src/main/java/it/geosolutions/geoserver/rest/HTTPUtils.java b/src/main/java/it/geosolutions/geoserver/rest/HTTPUtils.java
index f3cc6e6..d885dd0 100644
--- a/src/main/java/it/geosolutions/geoserver/rest/HTTPUtils.java
+++ b/src/main/java/it/geosolutions/geoserver/rest/HTTPUtils.java
@@ -25,6 +25,7 @@
package it.geosolutions.geoserver.rest;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -33,6 +34,11 @@ import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sf.json.JSON;
+import net.sf.json.JSONSerializer;
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpClient;
@@ -48,6 +54,9 @@ import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.commons.httpclient.methods.StringRequestEntity;
+import org.apache.commons.httpclient.methods.multipart.FilePart;
+import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
+import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -117,6 +126,22 @@ public class HTTPUtils {
return null;
}
+ /**
+ * Executes a request using the GET method and parses the result as a json object.
+ *
+ * @param path The path to request.
+ *
+ * @return The result parsed as json.
+ */
+ public static JSON getAsJSON(String url, String username, String pw) throws Exception {
+ String response = get(url, username, pw);
+ return json(response);
+ }
+
+ public static JSON json(String content) {
+ return JSONSerializer.toJSON(content);
+ }
+
/**
* PUTs a File to the given URL.
* Basic auth is used if both username and pw are not null.
@@ -175,6 +200,23 @@ public class HTTPUtils {
return put(url, content, "text/xml", username, pw);
}
+ /**
+ * PUTs a String representing an JSON Object to the given URL.
+ * Basic auth is used if both username and pw are not null.
+ *
+ * @param url The URL where to connect to.
+ * @param content The JSON Object to be sent as a String.
+ * @param username Basic auth credential. No basic auth if null.
+ * @param pw Basic auth credential. No basic auth if null.
+ * @return The HTTP response as a String if the HTTP response code was 200
+ * (OK).
+ * @throws MalformedURLException
+ * @return the HTTP response or null on errors.
+ */
+ public static String putJson(String url, String content, String username, String pw) {
+ return put(url, content, "application/json", username, pw);
+ }
+
/**
* Performs a PUT to the given URL.
* Basic auth is used if both username and pw are not null.
@@ -233,6 +275,38 @@ public class HTTPUtils {
}
}
+ /**
+ * POSTs a list of files as attachments to the given URL.
+ * Basic auth is used if both username and pw are not null.
+ *
+ * @param url The URL where to connect to.
+ * @param dir The folder containing the attachments.
+ * @param username Basic auth credential. No basic auth if null.
+ * @param pw Basic auth credential. No basic auth if null.
+ * @return The HTTP response as a String if the HTTP response code was 200
+ * (OK).
+ * @throws MalformedURLException
+ * @return the HTTP response or null on errors.
+ */
+ public static String postMultipartForm(String url, File dir, String username, String pw) {
+ try {
+ List
* Basic auth is used if both username and pw are not null.
@@ -250,6 +324,23 @@ public class HTTPUtils {
return post(url, content, "text/xml", username, pw);
}
+ /**
+ * POSTs a String representing an JSON Object to the given URL.
+ * Basic auth is used if both username and pw are not null.
+ *
+ * @param url The URL where to connect to.
+ * @param content The JSON content to be sent as a String.
+ * @param username Basic auth credential. No basic auth if null.
+ * @param pw Basic auth credential. No basic auth if null.
+ * @return The HTTP response as a String if the HTTP response code was 200
+ * (OK).
+ * @throws MalformedURLException
+ * @return the HTTP response or null on errors.
+ */
+ public static String postJson(String url, String content, String username, String pw) {
+ return post(url, content, "application/json", username, pw);
+ }
+
/**
* Performs a POST to the given URL.
* Basic auth is used if both username and pw are not null.
@@ -293,6 +384,7 @@ public class HTTPUtils {
httpMethod.setRequestEntity(requestEntity);
int status = client.executeMethod(httpMethod);
+ InputStream responseBody;
switch (status) {
case HttpURLConnection.HTTP_OK:
case HttpURLConnection.HTTP_CREATED:
@@ -303,9 +395,10 @@ public class HTTPUtils {
LOGGER.info("HTTP " + httpMethod.getStatusText() + ": " + response);
return response;
default:
+ responseBody = httpMethod.getResponseBodyAsStream();
LOGGER.warn("Bad response: code[" + status + "]" + " msg[" + httpMethod.getStatusText() + "]"
+ " url[" + url + "]" + " method[" + httpMethod.getClass().getSimpleName()
- + "]: " + IOUtils.toString(httpMethod.getResponseBodyAsStream()));
+ + "]: " + (responseBody != null ? IOUtils.toString(responseBody) : ""));
return null;
}
} catch (ConnectException e) {
diff --git a/src/main/java/it/geosolutions/geoserver/rest/manager/GeoServerRESTImporterManager.java b/src/main/java/it/geosolutions/geoserver/rest/manager/GeoServerRESTImporterManager.java
new file mode 100644
index 0000000..ccf39e1
--- /dev/null
+++ b/src/main/java/it/geosolutions/geoserver/rest/manager/GeoServerRESTImporterManager.java
@@ -0,0 +1,321 @@
+/*
+ * GeoServer-Manager - Simple Manager Library for GeoServer
+ *
+ * Copyright (C) 2007,2011 GeoSolutions S.A.S.
+ * http://www.geo-solutions.it
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package it.geosolutions.geoserver.rest.manager;
+
+import it.geosolutions.geoserver.rest.HTTPUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+
+import net.sf.json.JSON;
+import net.sf.json.JSONObject;
+
+import org.restlet.data.MediaType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Alessio Fabiani, GeoSolutions S.A.S.
+ *
+ */
+public class GeoServerRESTImporterManager extends GeoServerRESTAbstractManager {
+
+ private final static Logger LOGGER = LoggerFactory.getLogger(GeoServerRESTImporterManager.class);
+
+ /**
+ * Default constructor.
+ *
+ * @param restURL GeoServer REST API endpoint
+ * @param username GeoServer REST API authorized username
+ * @param password GeoServer REST API password for the former username
+ */
+ public GeoServerRESTImporterManager(URL restURL, String username, String password)
+ throws IllegalArgumentException {
+ super(restURL, username, password);
+ }
+
+ /**
+ * Retrieves the Import JSON Object given its identifier
+ *
+ * @param imp int: Import context number ID
+ */
+ public JSONObject getImport(int imp) throws Exception {
+ JSON json = HTTPUtils.getAsJSON(String.format(buildUrl()+"/%d", imp), gsuser , gspass);
+ return ((JSONObject)json).getJSONObject("import");
+ }
+
+ /**
+ * Retrieves the Import Task JSON Object given its identifier and task number
+ *
+ * @param imp int: Import context number ID
+ * @param task int: Task number
+ */
+ public JSONObject getTask(int imp, int task) throws Exception {
+ JSON json = HTTPUtils.getAsJSON(String.format(buildUrl()+"/%d/tasks/%d?expand=all", imp, task), gsuser , gspass);
+ return ((JSONObject)json).getJSONObject("task");
+ }
+
+ /**
+ * Example usage:
+ *
+ * // Creates a new Importer Context and gets back the ID
+ * int i = postNewImport();
+ *
+ * // Attaches to the new Importer Context a Task pointing to a shapefile's zip archive
+ * int t = postNewTaskAsMultiPartForm(i, "/path_to/shape/archsites_no_crs.zip");
+ *
+ * // Check that the Task was actually created and that the CRS has not recognized in this case
+ * JSONObject task = getTask(i, t);
+ * assertEquals("NO_CRS", task.getString("state"));
+ *
+ * // Prepare the JSON String instructing the Task about the SRS to use
+ * String json =
+ * "{" +
+ * "\"task\": {" +
+ * "\"layer\": {" +
+ * "\"srs\": \"EPSG:4326\"" +
+ * "}" +
+ * "}" +
+ * "}";
+ *
+ * // Performing the Task update
+ * putTask(i, t, json);
+ *
+ * // Double check that the Task is in the READY state
+ * task = getTask(i, t);
+ * assertEquals("READY", task.getString("state"));
+ * assertEquals("gs_archsites", task.getJSONObject("layer").getJSONObject("style").getString("name"));
+ *
+ * // Prepare the JSON String instructing the Task avout the SLD to use for the new Layer
+ * json =
+ * "{" +
+ * "\"task\": {" +
+ * "\"layer\": {" +
+ * "\"style\": {" +
+ * "\"name\": \"point\"" +
+ * "}" +
+ * "}" +
+ * "}" +
+ * "}";
+ *
+ * // Performing the Task update
+ * putTask(i, t,json);
+ *
+ * // Double check that the Task is in the READY state and that the Style has been correctly updated
+ * task = getTask(i, t);
+ * assertEquals("READY", task.getString("state"));
+ * assertEquals("point", task.getJSONObject("layer").getJSONObject("style").getString("name"));
+ *
+ * // Finally starts the Import ...
+ * postImport(i);
+ *
+ *
+ * @param imp int: Import context number ID
+ * @param task int: Task number
+ * @param json String: JSON containing the Task properties to be updated
+ * @throws Exception
+ */
+ public void putTask(int imp, int task, final String json) throws Exception {
+ //HTTPUtils.putJson(String.format(buildUrl()+"/%d/tasks/%d", imp, task), json, gsuser, gspass);
+ HTTPUtils.put(String.format(buildUrl()+"/%d/tasks/%d", imp, task), json, "text/plain", gsuser, gspass);
+ }
+
+ /**
+ * Just update the Layers properties associated to a Task (t) in the Importer Context (i).
+ *
+ * e.g.:
+ *
+ * putTaskLayer(i, t, "{\"title\":\"Archsites\", \"abstract\":\"Archeological Sites\"}");
+ *
+ *
+ * @param imp int: Import context number ID
+ * @param task int: Task number
+ * @param json String: JSON containing the Layer properties to be updated
+ * @throws Exception
+ */
+ public void putTaskLayer(int imp, int task, final String json) throws Exception {
+ HTTPUtils.putJson(String.format(buildUrl()+"/%d/tasks/%d/layer", imp, task), json, gsuser, gspass);
+ }
+
+ /**
+ * Just update the Layers properties associated to a Task (t) in the Importer Context (i).
+ *
+ * e.g.:
+ *
+ * putTaskLayer(i, t, "{\"title\":\"Archsites\", \"abstract\":\"Archeological Sites\"}");
+ *
+ *
+ * @param imp int: Import context number ID
+ * @param task int: Task number
+ * @param json String: JSON containing the Layer properties to be updated
+ * @throws Exception
+ */
+ public void postTaskTransform(int imp, int task, final String json) throws Exception {
+ HTTPUtils.postJson(String.format(buildUrl()+"/%d/tasks/%d/transforms", imp, task), json, gsuser, gspass);
+ }
+
+ /**
+ * Creates an empty Importer Context.
+ *
+ * @return The new Importer Context ID
+ * @throws Exception
+ */
+ public int postNewImport() throws Exception {
+ return postNewImport(null);
+ }
+
+ /**
+ * e.g.:
+ *
+ * String body =
+ * "{" +
+ * "\"import\": { " +
+ * "\"data\": {" +
+ * "\"type\": \"mosaic\", " +
+ * "\"time\": {" +
+ * " \"mode\": \"auto\"" +
+ * "}" +
+ * "}" +
+ * "}" +
+ * "}";
+ *
+ *
+ * @param body JSON String representing the Importer Context definition
+ * @return The new Importer Context ID
+ * @throws Exception
+ */
+ public int postNewImport(String body) throws Exception {
+ String resp = body == null ? HTTPUtils.post(buildUrl(), "", "text/plain", gsuser, gspass)
+ : HTTPUtils.postJson(buildUrl(), body, gsuser, gspass);
+
+ JSONObject json = (JSONObject) HTTPUtils.json(resp);
+ JSONObject imprt = json.getJSONObject("import");
+ return imprt.getInt("id");
+ }
+
+ /**
+ * Actually starts the READY State Import.
+ *
+ * @param imp int: Import context number ID
+ * @throws Exception
+ */
+ public void postImport(int imp) throws Exception {
+ HTTPUtils.post(buildUrl()+"/" + imp + "?exec=true", "", "text/plain", gsuser, gspass);
+ }
+
+ /**
+ *
+ * @param imp int: Import context number ID
+ * @param data
+ * @return
+ * @throws Exception
+ */
+ public int postNewTaskAsMultiPartForm(int imp, String data) throws Exception {
+ String resp = HTTPUtils.postMultipartForm(buildUrl()+"/" + imp + "/tasks", unpack(data), gsuser, gspass);
+
+ JSONObject json = (JSONObject) HTTPUtils.json(resp);
+
+ JSONObject task = json.getJSONObject("task");
+ return task.getInt("id");
+ }
+
+ /**
+ * Allows to attach a new zip file to an existing Importer Context.
+ *
+ * @param imp int: Import context number ID
+ * @param path
+ * @return
+ * @throws Exception
+ */
+ public int putNewTask(int imp, String path) throws Exception {
+ File zip = new File(path);
+
+ String resp = HTTPUtils.put(buildUrl()+"/" + imp + "/tasks/" + zip.getName(), zip, MediaType.APPLICATION_ZIP.toString(), gsuser, gspass);
+
+ JSONObject json = (JSONObject) HTTPUtils.json(resp);
+
+ JSONObject task = json.getJSONObject("task");
+ return task.getInt("id");
+ }
+
+ //=========================================================================
+ // Util methods
+ //=========================================================================
+
+ /**
+ * Creates the base REST URL for the imports
+ */
+ protected String buildUrl() {
+ StringBuilder sUrl = new StringBuilder(gsBaseUrl.toString()).append("/rest/imports");
+
+ return sUrl.toString();
+ }
+
+ /**
+ * Creates a temporary file
+ *
+ * @return Path to the temporary file
+ * @throws Exception
+ */
+ public static File tmpDir() throws Exception {
+ File dir = File.createTempFile("importer", "data", new File("target"));
+ dir.delete();
+ dir.mkdirs();
+ return dir;
+ }
+
+ /**
+ * Expands a zip archive into the temporary folder.
+ *
+ * @param path The absolute path to the source zip file
+ * @return Path to the temporary folder containing the expanded files
+ * @throws Exception
+ */
+ public static File unpack(String path) throws Exception {
+ return unpack(path, tmpDir());
+ }
+
+ /**
+ * Expands a zip archive into the target folder.
+ *
+ * @param path The absolute path to the source zip file
+ * @param dir Full path of the target folder where to expand the archive
+ * @return Path to the temporary folder containing the expanded files
+ * @throws Exception
+ */
+ public static File unpack(String path, File dir) throws Exception {
+
+ File file = new File(path);
+
+ //new VFSWorker().extractTo(file, dir);
+ if (!file.delete()) {
+ // fail early as tests will expect it's deleted
+ throw new IOException("deletion failed during extraction");
+ }
+
+ return dir;
+ }
+}
diff --git a/src/test/java/it/geosolutions/geoserver/rest/publisher/GeoserverRESTImporterTest.java b/src/test/java/it/geosolutions/geoserver/rest/publisher/GeoserverRESTImporterTest.java
new file mode 100644
index 0000000..bcb962a
--- /dev/null
+++ b/src/test/java/it/geosolutions/geoserver/rest/publisher/GeoserverRESTImporterTest.java
@@ -0,0 +1,97 @@
+/*
+ * GeoServer-Manager - Simple Manager Library for GeoServer
+ *
+ * Copyright (C) 2007,2011 GeoSolutions S.A.S.
+ * http://www.geo-solutions.it
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package it.geosolutions.geoserver.rest.publisher;
+
+import static org.junit.Assert.assertEquals;
+import it.geosolutions.geoserver.rest.GeoserverRESTTest;
+import net.sf.json.JSONObject;
+
+import org.junit.After;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.io.ClassPathResource;
+
+/**
+ * Testcase for publishing layers on geoserver.
+ * We need a running GeoServer to properly run the tests.
+ * If such geoserver instance cannot be contacted, tests will be skipped.
+ *
+ * @author Alessio Fabiani, GeoSolutions S.A.S.
+ *
+ */
+public class GeoserverRESTImporterTest extends GeoserverRESTTest {
+
+ private final static Logger LOGGER = LoggerFactory.getLogger(GeoserverRESTImporterTest.class);
+
+ @After
+ public void cleanUp() {
+ }
+
+ @Test
+ public void testShapeFileImport() throws Exception {
+ if (!enabled())
+ return;
+
+ // Creates a new Importer Context and gets back the ID
+ int i = publisher.postNewImport();
+
+ // Attaches to the new Importer Context a Task pointing to a shapefile's zip archive
+ String data = new ClassPathResource("testdata/test_noepsg.zip").getFile().getAbsolutePath();
+ int t = publisher.postNewTaskAsMultiPartForm(i, data);
+
+ // Check that the Task was actually created and that the CRS has not recognized in this case
+ JSONObject task = publisher.getTask(i, t);
+ //assertEquals("NO_CRS", task.getString("state"));
+ assertEquals("READY", task.getString("state"));
+
+ // Prepare the JSON String instructing the Task about the SRS to use
+ String json = "{\"layer\":{\"srs\":\"EPSG:26713\"}}";
+
+ // Performing the Task update
+ publisher.putTaskLayer(i, t, json);
+
+ // Double check that the Task is in the READY state
+ task = publisher.getTask(i, t);
+ assertEquals("READY", task.getString("state"));
+ assertEquals("nurc_10m_populated_places", task.getJSONObject("layer").getJSONObject("style").getString("name"));
+
+ // Prepare the JSON String instructing the Task avout the SLD to use for the new Layer
+ json = "{\"layer\":{\"style\":{\"name\": \"point\"}}}";
+
+ // Performing the Task update
+ publisher.putTaskLayer(i, t,json);
+
+ // Double check that the Task is in the READY state and that the Style has been correctly updated
+ task = publisher.getTask(i, t);
+ assertEquals("READY", task.getString("state"));
+ assertEquals("point", task.getJSONObject("layer").getJSONObject("style").getString("name"));
+
+ // Finally starts the Import ...
+ publisher.postImport(i);
+ }
+
+}