/* * Copyright (C) 2007-2012 Argeo GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 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.HashMap; import java.util.List; import java.util.Map; import javax.jcr.Credentials; import javax.jcr.LoginException; import javax.jcr.NoSuchWorkspaceException; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.Repository; import javax.jcr.RepositoryException; 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.api.JackrabbitRepository; import org.apache.jackrabbit.commons.NamespaceHelper; import org.apache.jackrabbit.commons.cnd.CndImporter; import org.argeo.jcr.ArgeoJcrConstants; import org.argeo.jcr.ArgeoJcrException; 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; import org.osgi.service.packageadmin.ExportedPackage; import org.osgi.service.packageadmin.PackageAdmin; import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; /** * Wrapper around a Jackrabbit repository which allows to simplify configuration * and intercept some actions. It exposes itself as a {@link Repository}. */ @Deprecated public class JackrabbitWrapper extends JcrRepositoryWrapper implements JackrabbitRepository, ResourceLoaderAware { private final static Log log = LogFactory.getLog(JackrabbitWrapper.class); private final static String DIGEST_ALGORITHM = "MD5"; // local private ResourceLoader resourceLoader; // 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 BundleContext bundleContext; /** * Explicitly set admin credentials used in initialization. Useful for * testing, in real applications authentication is rather dealt with * externally */ private Credentials adminCredentials = null; /** * Empty constructor, {@link #init()} should be called after properties have * been set */ public JackrabbitWrapper() { } @Override public void init() { prepareDataModel(); } /* * DATA MODEL */ /** * 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. */ protected void prepareDataModel() { if ((cndFiles == null || cndFiles.size() == 0) && (namespaces == null || namespaces.size() == 0)) return; Session session = null; try { session = login(adminCredentials); // 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 ArgeoJcrException("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; } } 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.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 ArgeoJcrException("Cannot process data model " + resUrl, e); } finally { 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 ArgeoJcrException("No " + resUrl + " in the classpath," + " make sure the containing" + " package is visible."); } return IOUtils.toByteArray(in); } catch (Exception e) { throw new ArgeoJcrException("Cannot read CND from " + resUrl, e); } finally { IOUtils.closeQuietly(in); } } /* * JACKRABBIT REPOSITORY IMPLEMENTATION */ @Override public Session login(Credentials credentials, String workspaceName, Map attributes) throws LoginException, NoSuchWorkspaceException, RepositoryException { // TODO Auto-generated method stub return null; } @Override public void shutdown() { // TODO Auto-generated method stub } /* * UTILITIES */ /** Find which OSGi bundle provided the data model resource */ protected Bundle findDataModelBundle(String resUrl) { 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 ArgeoJcrException("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 ArgeoJcrException("No OSGi exporting package found for " // + resUrl); } return exportingBundle; } /* * FIELDS ACCESS */ public void setNamespaces(Map namespaces) { this.namespaces = namespaces; } public void setCndFiles(List cndFiles) { this.cndFiles = cndFiles; } public void setBundleContext(BundleContext bundleContext) { this.bundleContext = bundleContext; } protected BundleContext getBundleContext() { return bundleContext; } public void setForceCndImport(Boolean forceCndUpdate) { this.forceCndImport = forceCndUpdate; } public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } public void setAdminCredentials(Credentials adminCredentials) { this.adminCredentials = adminCredentials; } }