changeset 79:f69e5b87f05f

Implementation to export artifacts as xml (applied patch from issue208 by SLT). artifacts/trunk@792 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Ingo Weinzierl <ingo.weinzierl@intevation.de>
date Tue, 16 Mar 2010 16:03:06 +0000
parents 55eefe63a777
children 8447467cef86
files ChangeLog artifact-database/doc/example-conf/conf.xml artifact-database/pom.xml artifact-database/src/main/java/de/intevation/artifactdatabase/App.java 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/FactoryBootstrap.java artifact-database/src/main/java/de/intevation/artifactdatabase/StringUtils.java artifact-database/src/main/java/de/intevation/artifactdatabase/rest/ExportResource.java artifact-database/src/main/java/de/intevation/artifactdatabase/rest/RestApp.java artifacts/src/main/java/de/intevation/artifacts/ArtifactDatabase.java
diffstat 11 files changed, 354 insertions(+), 39 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Thu Mar 11 10:53:59 2010 +0000
+++ b/ChangeLog	Tue Mar 16 16:03:06 2010 +0000
@@ -1,3 +1,47 @@
+2010-03-16  Ingo Weinzierl <ingo.weinzierl@intevation.de>
+
+	  Issue208
+
+	* artifact-database/doc/example-conf/conf.xml: Added a section to configure
+	  a secret string used to identify artifact imports for security reason.
+	  Incoming artifact imports need to be identified with this secret, otherwise
+	  the import should be blocked.
+	  Xpath for this secret: '/artifact-database/export-secret/text()'
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/FactoryBootstrap.java:
+	  New attribute 'exportSecret' and some code to read the secret from
+	  configuration while application startup.
+
+	* artifact-database/pom.xml: Added Apache Commons Codec library used to
+	  encode/decode artifact data.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/StringUtils.java:
+	  New utilities class for frequently used string operations.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java:
+	  Added some methods for exporting artifacts as xml documents. The data part
+	  is base64 encoded. A secret key is used while encoding this data to verify
+	  artifact imports, later.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java:
+	  Added an artifact loader interface used to deserialize artifacts. 
+	  getArtifact() implements such an ArtifactLoader to return
+	  PersistentArtifacts.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/rest/ExportResource.java:
+	  New. ServerResource to export artifacts (innerGet). This resource is
+	  available under "/export/{uuid}" via HTTP-GET request and returns an
+	  artifact specified by uuid as xml document.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/rest/RestApp.java:
+	  Register ExportResource to webserver.
+
+	* artifacts/src/main/java/de/intevation/artifacts/ArtifactDatabase.java:
+	  Added method to export artifacts.
+
+	* artifact-database/src/main/java/de/intevation/artifactdatabase/App.java:
+	  Reset bootstrap after application startup.
+
 2010-03-11	Sascha L. Teichmann	<sascha.teichmann@intevation.de>
 
 	* artifacts/src/main/java/de/intevation/artifacts/ArtifactNamespaceContext.java,
--- a/artifact-database/doc/example-conf/conf.xml	Thu Mar 11 10:53:59 2010 +0000
+++ b/artifact-database/doc/example-conf/conf.xml	Tue Mar 16 16:03:06 2010 +0000
@@ -1,5 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <artifact-database>
+    <!-- Change this! It's a very important security meassure to identify imports. -->
+    <export-secret>dMFhRZP4CYePgb1BSuVAhTnnh4kGGeENfe2YFyaq</export-secret>
     <factories>
         <context-factory>de.intevation.artifactdatabase.DefaultArtifactContextFactory</context-factory>
         <artifact-factories>
--- a/artifact-database/pom.xml	Thu Mar 11 10:53:59 2010 +0000
+++ b/artifact-database/pom.xml	Tue Mar 16 16:03:06 2010 +0000
@@ -70,6 +70,11 @@
       <version>8.3-603.jdbc4</version>
     </dependency>
     <dependency>
+      <groupId>commons-codec</groupId>
+      <artifactId>commons-codec</artifactId>
+      <version>1.4</version>
+    </dependency>
+    <dependency>
       <groupId>commons-dbcp</groupId>
       <artifactId>commons-dbcp</artifactId>
       <version>1.2.2</version>
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/App.java	Thu Mar 11 10:53:59 2010 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/App.java	Tue Mar 16 16:03:06 2010 +0000
@@ -48,6 +48,8 @@
         DatabaseCleaner cleaner = new DatabaseCleaner(
             bootstrap.getContext(), backend);
 
+        bootstrap = null;
+
         backend.setCleaner(cleaner);
 
         cleaner.setFilter(db);
@@ -57,4 +59,4 @@
         Standalone.startAsServer(db);
     }
 }
-// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8:
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java	Thu Mar 11 10:53:59 2010 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java	Tue Mar 16 16:03:06 2010 +0000
@@ -1,24 +1,34 @@
 package de.intevation.artifactdatabase;
 
+import de.intevation.artifactdatabase.Backend.PersistentArtifact;
+
+import de.intevation.artifacts.Artifact;
+import de.intevation.artifacts.ArtifactDatabase;
+import de.intevation.artifacts.ArtifactDatabaseException;
+import de.intevation.artifacts.ArtifactFactory;
+import de.intevation.artifacts.ArtifactNamespaceContext;
+import de.intevation.artifacts.CallContext;
+import de.intevation.artifacts.CallMeta;
+import de.intevation.artifacts.Service;
+import de.intevation.artifacts.ServiceFactory;
+
 import java.io.IOException;
 import java.io.OutputStream;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 
-import org.apache.log4j.Logger;
-import org.w3c.dom.Document;
+import org.apache.commons.codec.binary.Base64;
 
-import de.intevation.artifactdatabase.Backend.PersistentArtifact;
-import de.intevation.artifacts.Artifact;
-import de.intevation.artifacts.ArtifactDatabase;
-import de.intevation.artifacts.ArtifactDatabaseException;
-import de.intevation.artifacts.ArtifactFactory;
-import de.intevation.artifacts.CallContext;
-import de.intevation.artifacts.CallMeta;
-import de.intevation.artifacts.Service;
-import de.intevation.artifacts.ServiceFactory;
+import org.apache.log4j.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
 
 /**
  *  @author Sascha L. Teichmann
@@ -50,6 +60,9 @@
     public static final String NO_SUCH_SERVICE =
         "No such service";
 
+    public static final String DIGEST_ALGORITHM =
+        "SHA-1";
+
     public class CallContextImpl
     implements   CallContext
     {
@@ -171,6 +184,8 @@
     protected Backend     backend;
     protected Object      context;
 
+    protected byte []     exportSecret;
+
     protected HashSet     backgroundIds;
 
     public ArtifactDatabaseImpl() {
@@ -187,7 +202,8 @@
         setupArtifactFactories(bootstrap);
         setupServices(bootstrap);
 
-        context = bootstrap.getContext();
+        context      = bootstrap.getContext();
+        exportSecret = bootstrap.getExportSecret();
 
         wireWithBackend(backend);
     }
@@ -422,6 +438,82 @@
         return new DeferredOutputImpl(artifact, format, callMeta);
     }
 
+    public Document exportArtifact(String artifact, CallMeta callMeta)
+        throws ArtifactDatabaseException
+    {
+        final String [] factoryName = new String[1];
+
+        byte [] bytes = (byte [])backend.loadArtifact(
+            artifact,
+            new Backend.ArtifactLoader() {
+                public Object load(
+                    ArtifactFactory factory, 
+                    byte []         bytes,
+                    int             id
+                ) {
+                    factoryName[0] = factory.getName();
+                    return bytes;
+                }
+            });
+
+        if (bytes == null) {
+            throw new ArtifactDatabaseException(NO_SUCH_ARTIFACT);
+        }
+
+        return createExportDocument(
+            factoryName[0],
+            bytes,
+            exportSecret);
+    }
+
+    protected static Document createExportDocument(
+        String  factoryName,
+        byte [] artifact,
+        byte [] secret
+    ) {
+        Document document = XMLUtils.newDocument();
+
+        MessageDigest md;
+        try {
+            md = MessageDigest.getInstance(DIGEST_ALGORITHM);
+        }
+        catch (NoSuchAlgorithmException nsae) {
+            logger.error(nsae.getLocalizedMessage(), nsae);
+            return document;
+        }
+
+        md.update(artifact);
+        md.update(secret);
+
+        String checksum = StringUtils.toHex(md.digest());
+
+        XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
+            document,
+            ArtifactNamespaceContext.NAMESPACE_URI,
+            ArtifactNamespaceContext.NAMESPACE_PREFIX);
+
+        Element root = ec.create("result");
+        document.appendChild(root);
+
+        Element type = ec.create("type");
+        ec.addAttr(type, "name", "export");
+        root.appendChild(type);
+
+        Element data = ec.create("data");
+        ec.addAttr(data, "checksum", checksum);
+        data.setTextContent(Base64.encodeBase64String(artifact));
+
+        root.appendChild(data);
+
+        return document;
+    }
+
+    public Document importArtifact(Document data, CallMeta callMeta)
+        throws ArtifactDatabaseException
+    {
+        return null;
+    }
+
     public String [][] serviceNamesAndDescriptions() {
         return serviceNamesAndDescription;
     }
@@ -442,4 +534,4 @@
         return service.process(input, context, callMeta);
     }
 }
-// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8:
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java	Thu Mar 11 10:53:59 2010 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java	Tue Mar 16 16:03:06 2010 +0000
@@ -1,7 +1,5 @@
 package de.intevation.artifactdatabase;
 
-import java.util.UUID;
-
 import java.sql.Connection;
 import java.sql.SQLException;
 import java.sql.PreparedStatement;
@@ -17,7 +15,7 @@
 import org.apache.log4j.Logger;
 
 /**
- *  @author Sascha L. Teichmann
+ * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a>
  */
 public class Backend
 implements   DatabaseCleaner.ArtifactReviver
@@ -104,9 +102,8 @@
     }
 
     public String newIdentifier() {
-        UUID uuid = UUID.randomUUID();
         // TODO: check database for collisions.
-        return uuid.toString();
+        return StringUtils.newUUID();
     }
 
     public PersistentArtifact storeInitially(
@@ -122,13 +119,40 @@
             insertDatabase(artifact, factory, ttl));
     }
 
+    public interface ArtifactLoader {
+
+        Object load(ArtifactFactory factory, byte [] bytes, int id);
+
+    } // interface ArtifactLoader
+
+
     public PersistentArtifact getArtifact(String identifer) {
 
-        try {
-            UUID.fromString(identifer);
-        }
-        catch (IllegalArgumentException iae) {
-            logger.warn(iae.getLocalizedMessage());
+        return (PersistentArtifact)loadArtifact(
+            identifer, 
+            new ArtifactLoader() {
+
+                public Object load(
+                    ArtifactFactory factory,
+                    byte []         bytes,
+                    int             id
+                ) {
+                    ArtifactSerializer serializer = factory.getSerializer();
+
+                    Artifact artifact = serializer.fromBytes(bytes);
+
+                    return artifact == null
+                        ? null
+                        : new PersistentArtifact(artifact, serializer, id);
+                }
+            });
+    }
+
+
+
+    public Object loadArtifact(String identifer, ArtifactLoader loader) {
+
+        if (!StringUtils.checkUUID(identifer)) {
             return null;
         }
 
@@ -174,16 +198,9 @@
                 return null;
             }
 
-            ArtifactSerializer serializer =
-                factory.getSerializer();
-
             byte [] bytes = load_result.getBytes(5);
 
-            Artifact artifact = serializer.fromBytes(bytes);
-
-            return artifact == null
-                ? null
-                : new PersistentArtifact(artifact, serializer, id);
+            return loader.load(factory, bytes, id);
         }
         catch (SQLException sqle) {
             logger.error(sqle.getLocalizedMessage(), sqle);
@@ -206,7 +223,9 @@
     }
 
     protected void artifactOutdated(int id) {
-        logger.info("artifactOutdated: id = " + id);
+        if (logger.isDebugEnabled()) {
+            logger.info("artifactOutdated: id = " + id);
+        }
         if (cleaner != null) {
             cleaner.wakeup();
         }
@@ -391,4 +410,4 @@
         }
     }
 }
-// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8:
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifact-database/src/main/java/de/intevation/artifactdatabase/FactoryBootstrap.java	Thu Mar 11 10:53:59 2010 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/FactoryBootstrap.java	Tue Mar 16 16:03:06 2010 +0000
@@ -10,6 +10,7 @@
 
 import org.w3c.dom.Document;
 import org.w3c.dom.NodeList;
+
 /**
  * Bootstrap facility for the global context and the artifact factories.
  *
@@ -31,12 +32,21 @@
     public static final String SERVICE_FACTORIES =
         "/artifact-database/factories/service-factories/service-factory";
 
+    public static final String EXPORT_SECRET =
+        "/artifact-database/export-secret/text()";
+
+    public static final String DEFAULT_EXORT_SECRET =
+        "!!!CHANGE ME! I'M NO SECRET!!!";
+
     protected Object context;
 
     protected ArtifactFactory [] artifactFactories;
 
     protected ServiceFactory [] serviceFactories;
 
+    protected byte [] exportSecret;
+
+
     public FactoryBootstrap() {
     }
 
@@ -167,7 +177,19 @@
             new ServiceFactory[loadedFactories.size()]);
     }
 
+    protected void setupExportSecret() {
+        String secret = Config.getStringXPath(EXPORT_SECRET);
+
+        if (secret == null) {
+            logger.warn("NO EXPORT SECRET SET! USING INSECURE DEFAULT!");
+            secret = DEFAULT_EXORT_SECRET;
+        }
+
+        exportSecret = StringUtils.getUTF8Bytes(secret);
+    }
+
     public void boot() {
+        setupExportSecret();
         buildContext();
         loadArtifactFactories();
         loadServiceFactories();
@@ -184,5 +206,9 @@
     public Object getContext() {
         return context;
     }
+
+    public byte [] getExportSecret() {
+        return exportSecret;
+    }
 }
-// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8:
+// 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/StringUtils.java	Tue Mar 16 16:03:06 2010 +0000
@@ -0,0 +1,61 @@
+package de.intevation.artifactdatabase;
+
+import java.io.UnsupportedEncodingException;
+
+import java.util.UUID;
+
+import org.apache.log4j.Logger;
+
+/**
+ * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a>
+ */
+public final class StringUtils
+{
+    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();
+    }
+
+    public static final boolean checkUUID(String uuid) {
+        try {
+            UUID.fromString(uuid);
+        }
+        catch (IllegalArgumentException iae) {
+            logger.warn(iae.getLocalizedMessage());
+            return false;
+        }
+        return true;
+    }
+
+    public static final byte [] getUTF8Bytes(String s) {
+        try {
+            return s.getBytes("UTF-8");
+        }
+        catch (UnsupportedEncodingException usee) {
+            logger.error(usee.getLocalizedMessage(), usee);
+            return s.getBytes();
+        }
+    }
+}
+// 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/ExportResource.java	Tue Mar 16 16:03:06 2010 +0000
@@ -0,0 +1,58 @@
+package de.intevation.artifactdatabase.rest;
+
+import de.intevation.artifacts.ArtifactDatabase;
+import de.intevation.artifacts.ArtifactDatabaseException;
+
+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.restlet.resource.ResourceException;
+
+/**
+ * @author <a href="mailto:sascha.teichmann@intevation">Sascha L. Teichmann</a>
+ */
+public class ExportResource
+extends      BaseResource
+{
+    private static Logger logger = Logger.getLogger(ExportResource.class);
+
+    public static final String PATH = "/export/{uuid}";
+
+    protected Representation innerGet()
+    throws                   ResourceException
+    {
+        Request request = getRequest();
+
+        String identifier = (String)request.getAttributes().get("uuid");
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("looking for artifact id '" + identifier + "'");
+        }
+
+        ArtifactDatabase db = (ArtifactDatabase)getContext()
+            .getAttributes().get("database");
+
+        try {
+            return new DomRepresentation(
+                MediaType.APPLICATION_XML,
+                db.exportArtifact(identifier, 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	Thu Mar 11 10:53:59 2010 +0000
+++ b/artifact-database/src/main/java/de/intevation/artifactdatabase/rest/RestApp.java	Tue Mar 16 16:03:06 2010 +0000
@@ -5,8 +5,8 @@
 import java.util.concurrent.ConcurrentMap;
 
 import org.restlet.Application;
+import org.restlet.Context;
 import org.restlet.Restlet;
-import org.restlet.Context;
 
 import org.restlet.routing.Router;
 
@@ -41,8 +41,9 @@
         router.attach(CreateResource.PATH,      CreateResource.class);
         router.attach(ArtifactResource.PATH,    ArtifactResource.class);
         router.attach(ArtifactOutResource.PATH, ArtifactOutResource.class);
+        router.attach(ExportResource.PATH,      ExportResource.class);
 
         return router;
     }
 }
-// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8:
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifacts/src/main/java/de/intevation/artifacts/ArtifactDatabase.java	Thu Mar 11 10:53:59 2010 +0000
+++ b/artifacts/src/main/java/de/intevation/artifacts/ArtifactDatabase.java	Tue Mar 16 16:03:06 2010 +0000
@@ -43,10 +43,15 @@
     DeferredOutput out(String artifact, Document format, CallMeta callMeta)
         throws ArtifactDatabaseException;
 
+    Document exportArtifact(String artifact, CallMeta callMeta)
+        throws ArtifactDatabaseException;
+
+    Document importArtifact(Document data, CallMeta callMeta)
+        throws ArtifactDatabaseException;
+
     String [][] serviceNamesAndDescriptions();
 
     Document process(String service, Document input, CallMeta callMeta)
         throws ArtifactDatabaseException;
-
 }
-// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8:
+// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org