changeset 80:8447467cef86

Implementation to import artifacts from incoming xml documents (applied patch from issue208 by SLT). artifacts/trunk@799 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Ingo Weinzierl <ingo.weinzierl@intevation.de>
date Fri, 19 Mar 2010 09:34:40 +0000
parents f69e5b87f05f
children e9c80fdfee13
files ChangeLog artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java artifact-database/src/main/java/de/intevation/artifactdatabase/Config.java artifact-database/src/main/java/de/intevation/artifactdatabase/DatabaseCleaner.java artifact-database/src/main/java/de/intevation/artifactdatabase/Id.java artifact-database/src/main/java/de/intevation/artifactdatabase/SQL.java artifact-database/src/main/java/de/intevation/artifactdatabase/StringUtils.java artifact-database/src/main/java/de/intevation/artifactdatabase/rest/ImportResource.java artifact-database/src/main/java/de/intevation/artifactdatabase/rest/RestApp.java artifact-database/src/main/resources/sql/org-h2-driver.properties artifact-database/src/main/resources/sql/org-postgresql-driver.properties
diffstat 12 files changed, 388 insertions(+), 33 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Tue Mar 16 16:03:06 2010 +0000
+++ b/ChangeLog	Fri Mar 19 09:34:40 2010 +0000
@@ -1,6 +1,42 @@
+2010-03-19  Ingo Weinzierl <ingo.weinzierl@intevation.de>
+
+	  Issue208 (Artifact import)
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/rest/ImportResource.java:
+	  New server resource to import an artifact from an incoming xml document.
+	  The resource is available as /import and accepts xml documents send via
+	  HTTP POST.
+
+	* artifact-database/src/main/resources/sql/org-postgresql-driver.properties,
+	  artifact-database/src/main/resources/sql/org-h2-driver.properties: New sql
+	  statements to update an existing artifact.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java:
+	  New method to import an artifact from an incoming xml document. The data
+	  contained in this document is decoded using the secret.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java:
+	  New method to store an artifact into database or replace it if an artifact
+	  with the given id is already existing.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/StringUtils.java:
+	  Removed method to convert a byte array into a string (this is done by
+	  Apache's Hex class in commons codec) and added a new method to convert a
+	  string into a byte array using Apache commons codec as well.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/rest/RestApp.java:
+	  Register ImportResource to webserver.
+
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/DatabaseCleaner.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/SQL.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/Config.java,
+	  artifact-database/src/main/java/de/intevation/artifactdatabase/Id.java:
+	  Converted the author javadocs.
+
 2010-03-16  Ingo Weinzierl <ingo.weinzierl@intevation.de>
 
-	  Issue208
+	  Issue208 (Artifact export)
 
 	* artifact-database/doc/example-conf/conf.xml: Added a section to configure
 	  a secret string used to identify artifact imports for security reason.
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java	Tue Mar 16 16:03:06 2010 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java	Fri Mar 19 09:34:40 2010 +0000
@@ -7,6 +7,7 @@
 import de.intevation.artifacts.ArtifactDatabaseException;
 import de.intevation.artifacts.ArtifactFactory;
 import de.intevation.artifacts.ArtifactNamespaceContext;
+import de.intevation.artifacts.ArtifactSerializer;
 import de.intevation.artifacts.CallContext;
 import de.intevation.artifacts.CallMeta;
 import de.intevation.artifacts.Service;
@@ -19,11 +20,13 @@
 import java.security.NoSuchAlgorithmException;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 
 import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.binary.Hex;
 
 import org.apache.log4j.Logger;
 
@@ -31,7 +34,7 @@
 import org.w3c.dom.Element;
 
 /**
- *  @author Sascha L. Teichmann
+ * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a>
  */
 public class ArtifactDatabaseImpl
 implements   ArtifactDatabase, Id.Filter, Backend.FactoryLookup
@@ -63,6 +66,27 @@
     public static final String DIGEST_ALGORITHM =
         "SHA-1";
 
+    public static final String XPATH_IMPORT_CHECKSUM =
+        "/art:action/art:data/@checksum";
+
+    public static final String XPATH_IMPORT_FACTORY =
+        "/art:action/art:data/@factory";
+
+    public static final String XPATH_IMPORT_DATA =
+        "/art:action/art:data/text()";
+
+    public static final String INVALID_CHECKSUM =
+        "Invalid checksum";
+
+    public static final String CHECKSUM_MISMATCH =
+        "Mismatching checksum";
+
+    public static final String NO_DATA =
+        "No data";
+
+    public static final String INVALID_ARTIFACT =
+        "Invalid artifact";
+
     public class CallContextImpl
     implements   CallContext
     {
@@ -485,14 +509,14 @@
         md.update(artifact);
         md.update(secret);
 
-        String checksum = StringUtils.toHex(md.digest());
+        String checksum = Hex.encodeHexString(md.digest());
 
         XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
             document,
             ArtifactNamespaceContext.NAMESPACE_URI,
             ArtifactNamespaceContext.NAMESPACE_PREFIX);
 
-        Element root = ec.create("result");
+        Element root = ec.create("action");
         document.appendChild(root);
 
         Element type = ec.create("type");
@@ -501,6 +525,7 @@
 
         Element data = ec.create("data");
         ec.addAttr(data, "checksum", checksum);
+        ec.addAttr(data, "factory",  factoryName);
         data.setTextContent(Base64.encodeBase64String(artifact));
 
         root.appendChild(data);
@@ -508,10 +533,100 @@
         return document;
     }
 
-    public Document importArtifact(Document data, CallMeta callMeta)
+    public Document importArtifact(Document input, CallMeta callMeta)
         throws ArtifactDatabaseException
     {
-        return null;
+        String factoryName = XMLUtils.xpathString(
+            input,
+            XPATH_IMPORT_FACTORY,
+            ArtifactNamespaceContext.INSTANCE);
+
+        ArtifactFactory factory;
+
+        if (factoryName == null
+        || (factoryName = factoryName.trim()).length() == 0
+        || (factory = getArtifactFactory(factoryName)) == null) {
+            throw new ArtifactDatabaseException(NO_SUCH_FACTORY);
+        }
+
+        String checksumString = XMLUtils.xpathString(
+            input,
+            XPATH_IMPORT_CHECKSUM,
+            ArtifactNamespaceContext.INSTANCE);
+
+        byte [] checksum;
+
+        if (checksumString == null
+        || (checksumString = checksumString.trim()).length() == 0
+        || (checksum = StringUtils.decodeHex(checksumString)) == null
+        ) {
+            throw new ArtifactDatabaseException(INVALID_CHECKSUM);
+        }
+
+        checksumString = null;
+
+        String dataString = XMLUtils.xpathString(
+            input,
+            XPATH_IMPORT_DATA,
+            ArtifactNamespaceContext.INSTANCE);
+
+        if (dataString == null
+        || (dataString = dataString.trim()).length() == 0) {
+            throw new ArtifactDatabaseException(NO_DATA);
+        }
+
+        byte [] data = Base64.decodeBase64(dataString);
+
+        dataString = null;
+
+        MessageDigest md;
+        try {
+            md = MessageDigest.getInstance(DIGEST_ALGORITHM);
+        }
+        catch (NoSuchAlgorithmException nsae) {
+            logger.error(nsae.getLocalizedMessage(), nsae);
+            return XMLUtils.newDocument();
+        }
+
+        md.update(data);
+        md.update(exportSecret);
+
+        byte [] digest = md.digest();
+
+        if (!Arrays.equals(checksum, digest)) {
+            throw new ArtifactDatabaseException(CHECKSUM_MISMATCH);
+        }
+
+        ArtifactSerializer serializer = factory.getSerializer();
+
+        Artifact artifact = serializer.fromBytes(data); data = null;
+
+        if (artifact == null) {
+            throw new ArtifactDatabaseException(INVALID_ARTIFACT);
+        }
+
+        PersistentArtifact persistentArtifact;
+
+        try {
+            persistentArtifact = backend.storeOrReplace(
+                artifact,
+                factory,
+                factory.timeToLiveUntouched(artifact, context));
+        }
+        catch (Exception e) {
+            logger.error(e.getLocalizedMessage(), e);
+            throw new ArtifactDatabaseException(CREATION_FAILED);
+        }
+
+        CallContextImpl cc = new CallContextImpl(
+            persistentArtifact, CallContext.NOTHING, callMeta);
+
+        try {
+            return artifact.describe(input, cc);
+        }
+        finally {
+            cc.postCall();
+        }
     }
 
     public String [][] serviceNamesAndDescriptions() {
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java	Tue Mar 16 16:03:06 2010 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java	Fri Mar 19 09:34:40 2010 +0000
@@ -37,6 +37,12 @@
     public static final String SQL_LOAD_BY_GID =
         SQL.get("artifacts.select.gid");
 
+    public static final String SQL_GET_ID =
+        SQL.get("artifacts.get.id");
+
+    public static final String SQL_REPLACE =
+        SQL.get("artifacts.replace");
+
     protected DatabaseCleaner cleaner;
 
     protected FactoryLookup   factoryLookup;
@@ -119,13 +125,25 @@
             insertDatabase(artifact, factory, ttl));
     }
 
+    public PersistentArtifact storeOrReplace(
+        Artifact        artifact,
+        ArtifactFactory factory,
+        Long            ttl
+    )
+    throws Exception
+    {
+        return new PersistentArtifact(
+            artifact,
+            factory.getSerializer(),
+            storeOrReplaceDatabase(artifact, factory, ttl));
+    }
+
     public interface ArtifactLoader {
 
         Object load(ArtifactFactory factory, byte [] bytes, int id);
 
     } // interface ArtifactLoader
 
-
     public PersistentArtifact getArtifact(String identifer) {
 
         return (PersistentArtifact)loadArtifact(
@@ -148,8 +166,6 @@
             });
     }
 
-
-
     public Object loadArtifact(String identifer, ArtifactLoader loader) {
 
         if (!StringUtils.checkUUID(identifer)) {
@@ -249,6 +265,120 @@
         return serializer.fromBytes(bytes);
     }
 
+    protected int storeOrReplaceDatabase(
+        Artifact        artifact,
+        ArtifactFactory factory,
+        Long            ttl
+    ) {
+        String uuid = artifact.identifier();
+
+        if (!StringUtils.checkUUID(uuid)) {
+            throw new RuntimeException("No valid UUID");
+        }
+
+        Connection        connection = null;
+        PreparedStatement stmnt      = null;
+        ResultSet         result     = null;
+
+        DataSource dataSource = DBConnection.getDataSource();
+        try {
+            connection = dataSource.getConnection();
+            try {
+                connection.setAutoCommit(false);
+
+                stmnt = connection.prepareStatement(SQL_GET_ID);
+
+                stmnt.setString(1, uuid);
+                result = stmnt.executeQuery();
+
+                Integer ID = result.next()
+                    ? Integer.valueOf(result.getInt(1))
+                    : null;
+
+                result.close(); result = null;
+                stmnt.close();  stmnt  = null;
+
+                if (ID != null) { // already in database
+                    int id = ID.intValue();
+
+                    stmnt = connection.prepareStatement(SQL_REPLACE);
+
+                    if (ttl == null) {
+                        stmnt.setNull(1, Types.BIGINT);
+                    }
+                    else {
+                        stmnt.setLong(1, ttl.longValue());
+                    }
+
+                    stmnt.setString(2, factory.getName());
+                    stmnt.setBytes(
+                        3,
+                        factory.getSerializer().toBytes(artifact));
+                    stmnt.setInt(4, id);
+
+                    stmnt.execute();
+                    connection.commit();
+                    return id;
+                }
+
+                stmnt = connection.prepareStatement(SQL_NEXT_ID);
+                result = stmnt.executeQuery();
+
+                if (!result.next()) {
+                    throw new RuntimeException("No id generated");
+                }
+
+                int id = result.getInt(1);
+
+                result.close(); result = null;
+                stmnt.close();  stmnt  = null;
+
+                stmnt = connection.prepareStatement(SQL_INSERT);
+
+                stmnt.setInt(1, id);
+                stmnt.setString(2, uuid);
+                if (ttl == null) {
+                    stmnt.setNull(3, Types.BIGINT);
+                }
+                else {
+                    stmnt.setLong(3, ttl.longValue());
+                }
+
+                stmnt.setString(4, factory.getName());
+
+                stmnt.setBytes(
+                    5,
+                    factory.getSerializer().toBytes(artifact));
+
+                stmnt.execute();
+                connection.commit();
+                return id;
+            }
+            catch (SQLException sqle) {
+                connection.rollback();
+                throw sqle;
+            }
+        }
+        catch (SQLException sqle) {
+            logger.error(sqle.getLocalizedMessage(), sqle);
+        }
+        finally {
+            if (result != null) {
+                try { result.close(); }
+                catch (SQLException sqle) {}
+            }
+            if (stmnt != null) {
+                try { stmnt.close(); }
+                catch (SQLException sqle) {}
+            }
+            if (connection != null) {
+                try { connection.close(); }
+                catch (SQLException sqle) {}
+            }
+        }
+        throw new RuntimeException("failed insert artifact into database");
+    }
+
     protected int insertDatabase(
         Artifact        artifact,
         ArtifactFactory factory,
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/Config.java	Tue Mar 16 16:03:06 2010 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/Config.java	Fri Mar 19 09:34:40 2010 +0000
@@ -19,7 +19,7 @@
 import org.apache.log4j.Logger;
 
 /**
- *  @author Sascha L. Teichmann
+ * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a>
  */
 public final class Config
 {
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/DatabaseCleaner.java	Tue Mar 16 16:03:06 2010 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/DatabaseCleaner.java	Fri Mar 19 09:34:40 2010 +0000
@@ -15,7 +15,7 @@
 import java.util.List;
 
 /**
- *  @author Sascha L. Teichmann
+ * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a>
  */
 public class DatabaseCleaner
 extends      Thread
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/Id.java	Tue Mar 16 16:03:06 2010 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/Id.java	Fri Mar 19 09:34:40 2010 +0000
@@ -3,7 +3,7 @@
 import java.util.List;
 
 /**
- *  @author Sascha L. Teichmann
+ * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a>
  */
 public class Id
 {
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/SQL.java	Tue Mar 16 16:03:06 2010 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/SQL.java	Fri Mar 19 09:34:40 2010 +0000
@@ -8,7 +8,7 @@
 import org.apache.log4j.Logger;
 
 /**
- *  @author Sascha L. Teichmann
+ * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a>
  */
 public final class SQL
 {
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/StringUtils.java	Tue Mar 16 16:03:06 2010 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/StringUtils.java	Fri Mar 19 09:34:40 2010 +0000
@@ -4,6 +4,10 @@
 
 import java.util.UUID;
 
+import org.apache.commons.codec.DecoderException;
+
+import org.apache.commons.codec.binary.Hex;
+
 import org.apache.log4j.Logger;
 
 /**
@@ -13,26 +17,6 @@
 {
     private static Logger logger = Logger.getLogger(StringUtils.class);
 
-    private static final char [] HEX = {
-        '0', '1', '2', '3', '4', '5', '6', '7', 
-        '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
-    };
-
-    private StringUtils() {
-    }
-
-    public static final String toHex(byte [] bytes) {
-        char out [] = new char[bytes.length*2];
-
-        for (int i = 0, j = 0; i < bytes.length; ++i) {
-            byte b  = bytes[i];
-            out[j++] = HEX[(b >> 4) & 0xf];
-            out[j++] = HEX[ b       & 0xf];
-        }
-
-        return new String(out);
-    }
-
     public static final String newUUID() {
         return UUID.randomUUID().toString();
     }
@@ -57,5 +41,14 @@
             return s.getBytes();
         }
     }
+
+    public static final byte [] decodeHex(String s) {
+        try {
+            return Hex.decodeHex(s.toCharArray());
+        }
+        catch (DecoderException de) {
+            return null;
+        }
+    }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/rest/ImportResource.java	Fri Mar 19 09:34:40 2010 +0000
@@ -0,0 +1,66 @@
+package de.intevation.artifactdatabase.rest;
+
+import de.intevation.artifacts.ArtifactDatabase;
+import de.intevation.artifacts.ArtifactDatabaseException;
+
+import java.io.IOException;
+
+import org.apache.log4j.Logger;
+
+import org.restlet.data.MediaType;
+import org.restlet.data.Request;
+import org.restlet.data.Response;
+import org.restlet.data.Status;
+
+import org.restlet.ext.xml.DomRepresentation;
+
+import org.restlet.representation.EmptyRepresentation;
+import org.restlet.representation.Representation;
+
+import org.w3c.dom.Document;
+
+/**
+ * @author <a href="mailto:sascha.teichmann@intevation">Sascha L. Teichmann</a>
+ */
+public class ImportResource
+extends      BaseResource
+{
+    private static Logger logger = Logger.getLogger(ImportResource.class);
+
+    public static final String PATH = "/import";
+
+    protected Representation innerPost(Representation requestRepr) {
+
+        Document inputDocument = null;
+        try {
+            DomRepresentation input = new DomRepresentation(requestRepr);
+            input.setNamespaceAware(true);
+            inputDocument = input.getDocument();
+        }
+        catch (IOException ioe) {
+            logger.error(ioe.getMessage());
+            Response response = getResponse();
+            response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST, ioe);
+            return new EmptyRepresentation();
+        }
+
+        Request request = getRequest();
+
+        ArtifactDatabase db = (ArtifactDatabase)getContext()
+            .getAttributes().get("database");
+
+        try {
+            return new DomRepresentation(
+                MediaType.APPLICATION_XML,
+                db.importArtifact(inputDocument, getCallMeta()));
+        }
+        catch (ArtifactDatabaseException adbe) {
+            logger.warn(adbe.getLocalizedMessage(), adbe);
+            Response response = getResponse();
+            response.setStatus(
+                Status.CLIENT_ERROR_NOT_FOUND, adbe.getMessage());
+            return new EmptyRepresentation();
+        }
+    }
+}
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/rest/RestApp.java	Tue Mar 16 16:03:06 2010 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/rest/RestApp.java	Fri Mar 19 09:34:40 2010 +0000
@@ -42,6 +42,7 @@
         router.attach(ArtifactResource.PATH,    ArtifactResource.class);
         router.attach(ArtifactOutResource.PATH, ArtifactOutResource.class);
         router.attach(ExportResource.PATH,      ExportResource.class);
+        router.attach(ImportResource.PATH,      ImportResource.class);
 
         return router;
     }
--- a/artifact-database/src/main/resources/sql/org-h2-driver.properties	Tue Mar 16 16:03:06 2010 +0000
+++ b/artifact-database/src/main/resources/sql/org-h2-driver.properties	Fri Mar 19 09:34:40 2010 +0000
@@ -14,4 +14,11 @@
 
 artifacts.select.gid=SELECT id, last_access, ttl, factory, data FROM artifacts WHERE gid = ?
 
+artifacts.get.id=SELECT id FROM artifacts WHERE gid = ?
+
+artifacts.replace=UPDATE artifacts SET \
+    creation = CURRENT_TIMESTAMP, last_access = CURRENT_TIMESTAMP, \
+    ttl = ?, factory = ?, data = ? \
+    WHERE id = ?
+
 artifacts.delete=DELETE FROM artifacts WHERE id = ?
--- a/artifact-database/src/main/resources/sql/org-postgresql-driver.properties	Tue Mar 16 16:03:06 2010 +0000
+++ b/artifact-database/src/main/resources/sql/org-postgresql-driver.properties	Fri Mar 19 09:34:40 2010 +0000
@@ -14,4 +14,11 @@
 
 artifacts.select.gid=SELECT id, last_access, ttl, factory, data FROM artifacts WHERE gid = ?::uuid
 
+artifacts.get.id=SELECT id FROM artifacts WHERE gid = ?::uuid
+
+artifacts.replace=UPDATE artifacts SET \
+    creation = CURRENT_TIMESTAMP, last_access = CURRENT_TIMESTAMP, \
+    ttl = ?, factory = ?, data = ? \
+    WHERE id = ?
+
 artifacts.delete=DELETE FROM artifacts WHERE id = ?

http://dive4elements.wald.intevation.org