From 11dcf593dee796336eaa3f7063c6b36595d31aa6 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Mon, 10 Dec 2012 12:27:11 +0000 Subject: [PATCH] Store data model CND files git-svn-id: https://svn.argeo.org/commons/trunk@5942 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- .../org/argeo/util/security/DigestUtils.java | 13 + .../META-INF/spring/noderepo.xml | 1 + .../noderepo.properties | 1 + .../argeo/jackrabbit/JackrabbitContainer.java | 2 +- .../argeo/jackrabbit/JackrabbitWrapper.java | 294 ++++++++++-------- 5 files changed, 175 insertions(+), 136 deletions(-) diff --git a/base/runtime/org.argeo.util/src/main/java/org/argeo/util/security/DigestUtils.java b/base/runtime/org.argeo.util/src/main/java/org/argeo/util/security/DigestUtils.java index ade5b1e01..b6aae6fd4 100644 --- a/base/runtime/org.argeo.util/src/main/java/org/argeo/util/security/DigestUtils.java +++ b/base/runtime/org.argeo.util/src/main/java/org/argeo/util/security/DigestUtils.java @@ -33,6 +33,19 @@ public class DigestUtils { // TODO: make it writable private final static Integer byteBufferCapacity = 100 * 1024;// 100 KB + public static String digest(String algorithm, byte[] bytes) { + try { + MessageDigest digest = MessageDigest.getInstance(algorithm); + digest.update(bytes); + byte[] checksum = digest.digest(); + String res = StreamUtils.encodeHexString(checksum); + return res; + } catch (Exception e) { + throw new ArgeoException("Cannot digest with algorithm " + + algorithm, e); + } + } + public static String digest(String algorithm, InputStream in) { try { MessageDigest digest = MessageDigest.getInstance(algorithm); diff --git a/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo.xml b/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo.xml index b21d705ca..6648c1550 100644 --- a/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo.xml +++ b/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo.xml @@ -16,6 +16,7 @@ + diff --git a/server/modules/org.argeo.node.repo.jackrabbit/noderepo.properties b/server/modules/org.argeo.node.repo.jackrabbit/noderepo.properties index 7f01f8a9d..c91d51d08 100644 --- a/server/modules/org.argeo.node.repo.jackrabbit/noderepo.properties +++ b/server/modules/org.argeo.node.repo.jackrabbit/noderepo.properties @@ -1,6 +1,7 @@ # Workspace used by the node session argeo.node.repo.defaultWorkspace=main #argeo.node.repo.securityWorkspace=security +argeo.node.repo.forceCndImport=true # Repository base directory argeo.node.repo.home=${osgi.instance.area}/node diff --git a/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitContainer.java b/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitContainer.java index 87d62872a..9060b585a 100644 --- a/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitContainer.java +++ b/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitContainer.java @@ -52,7 +52,7 @@ import org.xml.sax.InputSource; */ public class JackrabbitContainer extends JackrabbitWrapper implements MaintainedRepository { - private Log log = LogFactory.getLog(JackrabbitContainer.class); + private final static Log log = LogFactory.getLog(JackrabbitContainer.class); // local private Resource configuration; diff --git a/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitWrapper.java b/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitWrapper.java index 669da15a1..1505b1cfe 100644 --- a/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitWrapper.java +++ b/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitWrapper.java @@ -15,6 +15,7 @@ */ package org.argeo.jackrabbit; +import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; @@ -29,6 +30,7 @@ import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.Repository; import javax.jcr.Session; +import javax.jcr.nodetype.NodeType; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; @@ -42,6 +44,7 @@ import org.argeo.jcr.ArgeoNames; import org.argeo.jcr.ArgeoTypes; import org.argeo.jcr.JcrRepositoryWrapper; import org.argeo.jcr.JcrUtils; +import org.argeo.util.security.DigestUtils; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; @@ -57,7 +60,8 @@ import org.springframework.core.io.ResourceLoader; */ public class JackrabbitWrapper extends JcrRepositoryWrapper implements ResourceLoaderAware { - private Log log = LogFactory.getLog(JackrabbitWrapper.class); + private final static Log log = LogFactory.getLog(JackrabbitWrapper.class); + private final static String DIGEST_ALGORITHM = "MD5"; // local private ResourceLoader resourceLoader; @@ -69,7 +73,7 @@ public class JackrabbitWrapper extends JcrRepositoryWrapper implements * Always import CNDs. Useful during development of new data models. In * production, explicit migration processes should be used. */ - private Boolean forceCndImport = false; + private Boolean forceCndImport = true; /** Namespaces to register: key is prefix, value namespace */ private Map namespaces = new HashMap(); @@ -120,152 +124,172 @@ public class JackrabbitWrapper extends JcrRepositoryWrapper implements // load CND files from classpath or as URL for (String resUrl : cndFiles) { - URL url; - Bundle dataModelBundle = null; - - {// TODO put this in a separate method - boolean classpath; - // normalize URL - if (bundleContext != null - && resUrl.startsWith("classpath:")) { - resUrl = resUrl.substring("classpath:".length()); - classpath = true; - } else if (resUrl.indexOf(':') < 0) { - if (!resUrl.startsWith("/")) { - resUrl = "/" + resUrl; - log.warn("Classpath should start with '/'"); - } - // resUrl = "classpath:" + resUrl; - classpath = true; - } else { - classpath = false; - } + processCndFile(session, resUrl); + } + } catch (Exception e) { + JcrUtils.discardQuietly(session); + throw new ArgeoException("Cannot import node type definitions " + + cndFiles, e); + } finally { + JcrUtils.logoutQuietly(session); + } - if (classpath) { - if (bundleContext != null) { - Bundle currentBundle = bundleContext.getBundle(); - url = currentBundle.getResource(resUrl); - if (url != null) {// found - dataModelBundle = findDataModelBundle(resUrl); - } - } else { - resUrl = "classpath:" + resUrl; - url = null; - // url = - // getClass().getClassLoader().getResource(resUrl); - // if (url == null) - // url = Thread.currentThread() - // .getContextClassLoader() - // .getResource(resUrl); - } - } else if (!resUrl.startsWith("classpath:")) { - url = new URL(resUrl); - } else { - url = null; - } - } - // check existing data model nodes - new NamespaceHelper(session).registerNamespace( - ArgeoNames.ARGEO, ArgeoNames.ARGEO_NAMESPACE); - if (!session - .itemExists(ArgeoJcrConstants.DATA_MODELS_BASE_PATH)) - JcrUtils.mkdirs(session, - ArgeoJcrConstants.DATA_MODELS_BASE_PATH); - Node dataModels = session - .getNode(ArgeoJcrConstants.DATA_MODELS_BASE_PATH); - NodeIterator it = dataModels.getNodes(); - Node dataModel = null; - while (it.hasNext()) { - Node node = it.nextNode(); - if (node.getProperty(ArgeoNames.ARGEO_URI).getString() - .equals(resUrl)) { - dataModel = node; - break; - } + } + + protected void processCndFile(Session session, String resUrl) { + Reader reader = null; + try { + // check existing data model nodes + new NamespaceHelper(session).registerNamespace(ArgeoNames.ARGEO, + ArgeoNames.ARGEO_NAMESPACE); + if (!session.itemExists(ArgeoJcrConstants.DATA_MODELS_BASE_PATH)) + JcrUtils.mkdirs(session, + ArgeoJcrConstants.DATA_MODELS_BASE_PATH); + Node dataModels = session + .getNode(ArgeoJcrConstants.DATA_MODELS_BASE_PATH); + NodeIterator it = dataModels.getNodes(); + Node dataModel = null; + while (it.hasNext()) { + Node node = it.nextNode(); + if (node.getProperty(ArgeoNames.ARGEO_URI).getString() + .equals(resUrl)) { + dataModel = node; + break; } + } - // does nothing if data model already registered - if (dataModel != null && !forceCndImport) { - if (dataModelBundle != null) { - String version = dataModel.getProperty( - ArgeoNames.ARGEO_DATA_MODEL_VERSION) - .getString(); - String dataModelBundleVersion = dataModelBundle - .getVersion().toString(); - if (!version.equals(dataModelBundleVersion)) { - log.warn("Data model with version " - + dataModelBundleVersion - + " available, current version is " - + version); - } + byte[] cndContent = readCndContent(resUrl); + String newDigest = DigestUtils.digest(DIGEST_ALGORITHM, cndContent); + Bundle bundle = findDataModelBundle(resUrl); + + String currentVersion = null; + if (dataModel != null) { + currentVersion = dataModel.getProperty( + ArgeoNames.ARGEO_DATA_MODEL_VERSION).getString(); + if (dataModel.hasNode(Node.JCR_CONTENT)) { + String oldDigest = JcrUtils.checksumFile(dataModel, + DIGEST_ALGORITHM); + if (oldDigest.equals(newDigest)) { + if (log.isDebugEnabled()) + log.debug("Data model " + resUrl + + " hasn't changed, keeping version " + + currentVersion); + return; } - // do not implicitly update - return; } + } - InputStream in = null; - Reader reader = null; - try { - if (url != null) { - in = url.openStream(); - } else if (resourceLoader != null) { - Resource res = resourceLoader.getResource(resUrl); - in = res.getInputStream(); - url = res.getURL(); - } else { - throw new ArgeoException("No " + resUrl - + " in the classpath," - + " make sure the containing" - + " package is visible."); - } + if (dataModel != null && !forceCndImport) { + log.info("Data model " + + resUrl + + " has changed since version " + + currentVersion + + (bundle != null ? ": version " + bundle.getVersion() + + ", bundle " + bundle.getSymbolicName() : "")); + return; + } - reader = new InputStreamReader(in); - // actually imports the CND - CndImporter.registerNodeTypes(reader, session, true); - - // FIXME: what if argeo.cnd would not be the first called on - // a new repo? argeo:dataModel would not be found - String fileName = FilenameUtils.getName(url.getPath()); - if (dataModel == null) { - dataModel = dataModels.addNode(fileName); - dataModel.addMixin(ArgeoTypes.ARGEO_DATA_MODEL); - dataModel.setProperty(ArgeoNames.ARGEO_URI, resUrl); - } else { - session.getWorkspace().getVersionManager() - .checkout(dataModel.getPath()); - } - if (dataModelBundle != null) - dataModel.setProperty( - ArgeoNames.ARGEO_DATA_MODEL_VERSION, - dataModelBundle.getVersion().toString()); - else - dataModel.setProperty( - ArgeoNames.ARGEO_DATA_MODEL_VERSION, "0.0.0"); - JcrUtils.updateLastModified(dataModel); - session.save(); - session.getWorkspace().getVersionManager() - .checkin(dataModel.getPath()); - } finally { - IOUtils.closeQuietly(in); - IOUtils.closeQuietly(reader); - } + reader = new InputStreamReader(new ByteArrayInputStream(cndContent)); + // actually imports the CND + CndImporter.registerNodeTypes(reader, session, true); + + if (dataModel != null & !dataModel.isNodeType(NodeType.NT_FILE)) { + dataModel.remove(); + dataModel = null; + } - if (log.isDebugEnabled()) - log.debug("Data model " - + resUrl - + (dataModelBundle != null ? ", version " - + dataModelBundle.getVersion() - + ", bundle " - + dataModelBundle.getSymbolicName() : "")); + // FIXME: what if argeo.cnd would not be the first called on + // a new repo? argeo:dataModel would not be found + String fileName = FilenameUtils.getName(resUrl); + if (dataModel == null) { + dataModel = dataModels.addNode(fileName, NodeType.NT_FILE); + dataModel.addNode(Node.JCR_CONTENT, NodeType.NT_RESOURCE); + dataModel.addMixin(ArgeoTypes.ARGEO_DATA_MODEL); + dataModel.setProperty(ArgeoNames.ARGEO_URI, resUrl); + } else { + session.getWorkspace().getVersionManager() + .checkout(dataModel.getPath()); } + if (bundle != null) + dataModel.setProperty(ArgeoNames.ARGEO_DATA_MODEL_VERSION, + bundle.getVersion().toString()); + else + dataModel.setProperty(ArgeoNames.ARGEO_DATA_MODEL_VERSION, + "0.0.0"); + JcrUtils.copyBytesAsFile(dataModel.getParent(), fileName, + cndContent); + JcrUtils.updateLastModified(dataModel); + session.save(); + session.getWorkspace().getVersionManager() + .checkin(dataModel.getPath()); + + if (currentVersion == null) + log.info("Data model " + + resUrl + + (bundle != null ? ", version " + bundle.getVersion() + + ", bundle " + bundle.getSymbolicName() : "")); + else + log.info("Data model " + + resUrl + + " updated from version " + + currentVersion + + (bundle != null ? ", version " + bundle.getVersion() + + ", bundle " + bundle.getSymbolicName() : "")); } catch (Exception e) { - JcrUtils.discardQuietly(session); - throw new ArgeoException("Cannot import node type definitions " - + cndFiles, e); + throw new ArgeoException("Cannot process data model " + resUrl, e); } finally { - JcrUtils.logoutQuietly(session); + IOUtils.closeQuietly(reader); } + } + + protected byte[] readCndContent(String resUrl) { + InputStream in = null; + try { + boolean classpath; + // normalize URL + if (bundleContext != null && resUrl.startsWith("classpath:")) { + resUrl = resUrl.substring("classpath:".length()); + classpath = true; + } else if (resUrl.indexOf(':') < 0) { + if (!resUrl.startsWith("/")) { + resUrl = "/" + resUrl; + log.warn("Classpath should start with '/'"); + } + classpath = true; + } else { + classpath = false; + } + + URL url = null; + if (classpath) { + if (bundleContext != null) { + Bundle currentBundle = bundleContext.getBundle(); + url = currentBundle.getResource(resUrl); + } else { + resUrl = "classpath:" + resUrl; + url = null; + } + } else if (!resUrl.startsWith("classpath:")) { + url = new URL(resUrl); + } + if (url != null) { + in = url.openStream(); + } else if (resourceLoader != null) { + Resource res = resourceLoader.getResource(resUrl); + in = res.getInputStream(); + url = res.getURL(); + } else { + throw new ArgeoException("No " + resUrl + " in the classpath," + + " make sure the containing" + " package is visible."); + } + + return IOUtils.toByteArray(in); + } catch (Exception e) { + throw new ArgeoException("Cannot read CND from " + resUrl, e); + } finally { + IOUtils.closeQuietly(in); + } } /* -- 2.30.2