# HG changeset patch # User Ingo Weinzierl # Date 1268991280 0 # Node ID 8447467cef869cd7d34808cb5ca59e6e4d1f6077 # Parent f69e5b87f05f8d06d59c1f01a89b4867d08b82d1 Implementation to import artifacts from incoming xml documents (applied patch from issue208 by SLT). artifacts/trunk@799 c6561f87-3c4e-4783-a992-168aeb5c3f6f diff -r f69e5b87f05f -r 8447467cef86 ChangeLog --- 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 + + 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 - 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. diff -r f69e5b87f05f -r 8447467cef86 artifact-database/src/main/java/de/intevation/artifactdatabase/ArtifactDatabaseImpl.java --- 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 Sascha L. Teichmann */ 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() { diff -r f69e5b87f05f -r 8447467cef86 artifact-database/src/main/java/de/intevation/artifactdatabase/Backend.java --- 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, diff -r f69e5b87f05f -r 8447467cef86 artifact-database/src/main/java/de/intevation/artifactdatabase/Config.java --- 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 Sascha L. Teichmann */ public final class Config { diff -r f69e5b87f05f -r 8447467cef86 artifact-database/src/main/java/de/intevation/artifactdatabase/DatabaseCleaner.java --- 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 Sascha L. Teichmann */ public class DatabaseCleaner extends Thread diff -r f69e5b87f05f -r 8447467cef86 artifact-database/src/main/java/de/intevation/artifactdatabase/Id.java --- 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 Sascha L. Teichmann */ public class Id { diff -r f69e5b87f05f -r 8447467cef86 artifact-database/src/main/java/de/intevation/artifactdatabase/SQL.java --- 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 Sascha L. Teichmann */ public final class SQL { diff -r f69e5b87f05f -r 8447467cef86 artifact-database/src/main/java/de/intevation/artifactdatabase/StringUtils.java --- 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 : diff -r f69e5b87f05f -r 8447467cef86 artifact-database/src/main/java/de/intevation/artifactdatabase/rest/ImportResource.java --- /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 Sascha L. Teichmann + */ +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 : diff -r f69e5b87f05f -r 8447467cef86 artifact-database/src/main/java/de/intevation/artifactdatabase/rest/RestApp.java --- 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; } diff -r f69e5b87f05f -r 8447467cef86 artifact-database/src/main/resources/sql/org-h2-driver.properties --- 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 = ? diff -r f69e5b87f05f -r 8447467cef86 artifact-database/src/main/resources/sql/org-postgresql-driver.properties --- 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 = ?