# HG changeset patch # User Ingo Weinzierl # Date 1268755386 0 # Node ID f69e5b87f05f8d06d59c1f01a89b4867d08b82d1 # Parent 55eefe63a777d6e6a9d919b9b9fb4c82d07765d5 Implementation to export artifacts as xml (applied patch from issue208 by SLT). artifacts/trunk@792 c6561f87-3c4e-4783-a992-168aeb5c3f6f diff -r 55eefe63a777 -r f69e5b87f05f ChangeLog --- 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 + + 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 * artifacts/src/main/java/de/intevation/artifacts/ArtifactNamespaceContext.java, diff -r 55eefe63a777 -r f69e5b87f05f artifact-database/doc/example-conf/conf.xml --- 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 @@ + + dMFhRZP4CYePgb1BSuVAhTnnh4kGGeENfe2YFyaq de.intevation.artifactdatabase.DefaultArtifactContextFactory diff -r 55eefe63a777 -r f69e5b87f05f artifact-database/pom.xml --- 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 @@ 8.3-603.jdbc4 + commons-codec + commons-codec + 1.4 + + commons-dbcp commons-dbcp 1.2.2 diff -r 55eefe63a777 -r f69e5b87f05f artifact-database/src/main/java/de/intevation/artifactdatabase/App.java --- 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 : diff -r 55eefe63a777 -r f69e5b87f05f artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java --- 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 : diff -r 55eefe63a777 -r f69e5b87f05f artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java --- 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 Sascha L. Teichmann */ 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 : diff -r 55eefe63a777 -r f69e5b87f05f artifact-database/src/main/java/de/intevation/artifactdatabase/FactoryBootstrap.java --- 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 : diff -r 55eefe63a777 -r f69e5b87f05f artifact-database/src/main/java/de/intevation/artifactdatabase/StringUtils.java --- /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 Sascha L. Teichmann + */ +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 : diff -r 55eefe63a777 -r f69e5b87f05f artifact-database/src/main/java/de/intevation/artifactdatabase/rest/ExportResource.java --- /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 Sascha L. Teichmann + */ +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 : diff -r 55eefe63a777 -r f69e5b87f05f artifact-database/src/main/java/de/intevation/artifactdatabase/rest/RestApp.java --- 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 : diff -r 55eefe63a777 -r f69e5b87f05f artifacts/src/main/java/de/intevation/artifacts/ArtifactDatabase.java --- 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 :