X-Git-Url: http://git.argeo.org/?a=blobdiff_plain;f=org.argeo.server.jcr%2Fsrc%2Forg%2Fargeo%2Fjackrabbit%2FJackrabbitDataModel.java;fp=org.argeo.server.jcr%2Fsrc%2Forg%2Fargeo%2Fjackrabbit%2FJackrabbitDataModel.java;h=b342d18bd21b00a1200472d16c06b9317c993f38;hb=1bf461a2e8da459f8c2caf080707248bb1977365;hp=0000000000000000000000000000000000000000;hpb=0b8aa4c76cb7a1d19abf93a4c1ae0c973abdab5b;p=lgpl%2Fargeo-commons.git diff --git a/org.argeo.server.jcr/src/org/argeo/jackrabbit/JackrabbitDataModel.java b/org.argeo.server.jcr/src/org/argeo/jackrabbit/JackrabbitDataModel.java new file mode 100644 index 000000000..b342d18bd --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jackrabbit/JackrabbitDataModel.java @@ -0,0 +1,283 @@ +package org.argeo.jackrabbit; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +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; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.jackrabbit.commons.NamespaceHelper; +import org.apache.jackrabbit.commons.cnd.CndImporter; +import org.argeo.ArgeoException; +import org.argeo.jcr.ArgeoJcrConstants; +import org.argeo.jcr.ArgeoNames; +import org.argeo.jcr.ArgeoTypes; +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; +import org.osgi.service.packageadmin.ExportedPackage; +import org.osgi.service.packageadmin.PackageAdmin; + +public class JackrabbitDataModel { + private final static Log log = LogFactory.getLog(JackrabbitDataModel.class); + private final static String DIGEST_ALGORITHM = "MD5"; + final static String[] DEFAULT_CNDS = { "/org/argeo/jcr/argeo.cnd", "/org/argeo/cms/cms.cnd" }; + + // data model + /** Node type definitions in CND format */ + private List cndFiles = new ArrayList(); + /** + * Always import CNDs. Useful during development of new data models. In + * production, explicit migration processes should be used. + */ + private Boolean forceCndImport = true; + + /** Namespaces to register: key is prefix, value namespace */ + private Map namespaces = new HashMap(); + + private final BundleContext bc; + + public JackrabbitDataModel(BundleContext bc) { + this.bc = bc; + } + + /** + * Import declared node type definitions and register namespaces. Tries to + * update the node definitions if they have changed. In case of failures an + * error will be logged but no exception will be thrown. + */ + public void prepareDataModel(Repository repository) { + cndFiles = Arrays.asList(DEFAULT_CNDS); + if ((cndFiles == null || cndFiles.size() == 0) && (namespaces == null || namespaces.size() == 0)) + return; + + Session session = null; + try { + session = repository.login(); + // register namespaces + if (namespaces.size() > 0) { + NamespaceHelper namespaceHelper = new NamespaceHelper(session); + namespaceHelper.registerNamespaces(namespaces); + } + + // load CND files from classpath or as URL + for (String resUrl : cndFiles) { + processCndFile(session, resUrl); + } + } catch (Exception e) { + JcrUtils.discardQuietly(session); + throw new ArgeoException("Cannot import node type definitions " + cndFiles, e); + } finally { + JcrUtils.logoutQuietly(session); + } + + } + + 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; + } + } + + Bundle bundle = findDataModelBundle(resUrl); + + byte[] cndContent = readCndContent(resUrl); + String newDigest = DigestUtils.digest(DIGEST_ALGORITHM, cndContent); + + 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.isTraceEnabled()) + log.trace("Data model " + resUrl + " hasn't changed, keeping version " + currentVersion); + return; + } + } + } + + 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(new ByteArrayInputStream(cndContent)); + // actually imports the CND + try { + CndImporter.registerNodeTypes(reader, session, true); + } catch (Exception e) { + log.error("Cannot import data model " + resUrl, e); + return; + } + + if (dataModel != null && !dataModel.isNodeType(NodeType.NT_FILE)) { + dataModel.remove(); + dataModel = null; + } + + // 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) { + throw new ArgeoException("Cannot process data model " + resUrl, e); + } finally { + IOUtils.closeQuietly(reader); + } + } + + protected byte[] readCndContent(String resUrl) { + BundleContext bundleContext = bc; + 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); + } + } + + /* + * UTILITIES + */ + /** Find which OSGi bundle provided the data model resource */ + protected Bundle findDataModelBundle(String resUrl) { + BundleContext bundleContext = bc; + if (bundleContext == null) + return null; + + if (resUrl.startsWith("/")) + resUrl = resUrl.substring(1); + String pkg = resUrl.substring(0, resUrl.lastIndexOf('/')).replace('/', '.'); + ServiceReference paSr = bundleContext.getServiceReference(PackageAdmin.class); + PackageAdmin packageAdmin = (PackageAdmin) bundleContext.getService(paSr); + + // find exported package + ExportedPackage exportedPackage = null; + ExportedPackage[] exportedPackages = packageAdmin.getExportedPackages(pkg); + if (exportedPackages == null) + throw new ArgeoException("No exported package found for " + pkg); + for (ExportedPackage ep : exportedPackages) { + for (Bundle b : ep.getImportingBundles()) { + if (b.getBundleId() == bundleContext.getBundle().getBundleId()) { + exportedPackage = ep; + break; + } + } + } + + Bundle exportingBundle = null; + if (exportedPackage != null) { + exportingBundle = exportedPackage.getExportingBundle(); + } else { + // assume this is in the same bundle + exportingBundle = bundleContext.getBundle(); + // throw new ArgeoException("No OSGi exporting package found for " + // + resUrl); + } + return exportingBundle; + } + +}