Work on backups and file metadata indexing.
authorMathieu Baudier <mbaudier@argeo.org>
Sun, 26 Jan 2020 21:21:00 +0000 (22:21 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Sun, 26 Jan 2020 21:21:00 +0000 (22:21 +0100)
org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeployment.java
org.argeo.cms/src/org/argeo/cms/internal/kernel/JackrabbitLocalRepository.java [new file with mode: 0644]
org.argeo.jcr/src/org/argeo/jcr/JcrRepositoryWrapper.java
org.argeo.maintenance/src/org/argeo/maintenance/backup/BackupContentHandler.java [new file with mode: 0644]
org.argeo.maintenance/src/org/argeo/maintenance/backup/LogicalBackup.java [new file with mode: 0644]
org.argeo.maintenance/src/org/argeo/maintenance/internal/Activator.java [new file with mode: 0644]
org.argeo.osgi.boot/.gitignore
org.argeo.osgi.boot/src/org/argeo/osgi/boot/internal/springutil/Bootstrap.java [new file with mode: 0644]

index 59d8c910f9640c287d564d924ac5da48db1a378a..f09a008072104b90050fb64feb221a16ba023766 100644 (file)
@@ -402,7 +402,11 @@ public class CmsDeployment implements NodeDeployment {
                properties.put(NodeConstants.CN, dataModelName);
                if (dataModelName.equals(NodeConstants.NODE))
                        properties.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE);
-               LocalRepository localRepository = new LocalRepository(repository, dataModelName);
+               LocalRepository localRepository;
+               if (repository instanceof RepositoryImpl)
+                       localRepository = new JackrabbitLocalRepository((RepositoryImpl) repository, dataModelName);
+               else
+                       localRepository = new LocalRepository(repository, dataModelName);
                bc.registerService(Repository.class, localRepository, properties);
                if (log.isTraceEnabled())
                        log.trace("Published data model " + dataModelName);
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/JackrabbitLocalRepository.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/JackrabbitLocalRepository.java
new file mode 100644 (file)
index 0000000..dc47e6e
--- /dev/null
@@ -0,0 +1,201 @@
+package org.argeo.cms.internal.kernel;
+
+import java.util.GregorianCalendar;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventIterator;
+import javax.jcr.observation.EventListener;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jackrabbit.api.JackrabbitValue;
+import org.apache.jackrabbit.core.RepositoryImpl;
+import org.argeo.jcr.JcrUtils;
+
+class JackrabbitLocalRepository extends LocalRepository {
+       private final static Log log = LogFactory.getLog(JackrabbitLocalRepository.class);
+       private final static String MIX_ETAG = "mix:etag";
+       private final static String JCR_ETAG = "jcr:etag";
+
+       private Map<String, WorkspaceMonitor> workspaceMonitors = new TreeMap<>();
+
+       public JackrabbitLocalRepository(RepositoryImpl repository, String cn) {
+               super(repository, cn);
+               Session session = KernelUtils.openAdminSession(repository);
+               try {
+                       for (String workspaceName : session.getWorkspace().getAccessibleWorkspaceNames()) {
+                               addMonitor(workspaceName);
+                       }
+               } catch (RepositoryException e) {
+                       throw new IllegalStateException(e);
+               } finally {
+                       JcrUtils.logoutQuietly(session);
+               }
+       }
+
+       protected RepositoryImpl getJackrabbitrepository(String workspaceName) {
+               return (RepositoryImpl) getRepository(workspaceName);
+       }
+
+       @Override
+       protected synchronized void processNewSession(Session session, String workspaceName) {
+               String realWorkspaceName = session.getWorkspace().getName();
+               addMonitor(realWorkspaceName);
+       }
+
+       private void addMonitor(String realWorkspaceName) {
+               if (!workspaceMonitors.containsKey(realWorkspaceName)) {
+                       try {
+                               WorkspaceMonitor workspaceMonitor = new WorkspaceMonitor(getJackrabbitrepository(realWorkspaceName),
+                                               getCn(), realWorkspaceName);
+                               workspaceMonitors.put(realWorkspaceName, workspaceMonitor);
+                               workspaceMonitor.start();
+                               if (log.isDebugEnabled())
+                                       log.debug("Registered " + workspaceMonitor.getName());
+                       } catch (RepositoryException e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       }
+               }
+       }
+
+       static String toEtag(Value v) {
+               JackrabbitValue value = (JackrabbitValue) v;
+               return '\"' + value.getContentIdentity() + '\"';
+
+       }
+
+       /** recursive */
+       static void setLastModified(Node node, Event event) throws RepositoryException {
+               GregorianCalendar calendar = new GregorianCalendar();
+               calendar.setTimeInMillis(event.getDate());
+               if (node.isNodeType(NodeType.NT_FOLDER)) {
+                       node.addMixin(NodeType.MIX_LAST_MODIFIED);
+               }
+               if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) {
+                       node.setProperty(Property.JCR_LAST_MODIFIED, calendar);
+                       node.setProperty(Property.JCR_LAST_MODIFIED_BY, event.getUserID());
+               }
+               if (node.getDepth() == 0)
+                       return;
+               Node parent = node.getParent();
+               setLastModified(parent, event);
+       }
+
+       static class WorkspaceMonitor extends Thread implements EventListener {
+               String workspaceName;
+               RepositoryImpl repositoryImpl;
+               Session session;
+
+               public WorkspaceMonitor(RepositoryImpl repositoryImpl, String cn, String workspaceName)
+                               throws RepositoryException {
+                       super("Monitor workspace " + workspaceName + " of repository " + cn);
+                       this.workspaceName = workspaceName;
+                       this.repositoryImpl = repositoryImpl;
+               }
+
+               public void run() {
+
+                       session = KernelUtils.openAdminSession(repositoryImpl, workspaceName);
+                       try {
+                               String[] nodeTypes = { NodeType.NT_FILE, NodeType.MIX_LAST_MODIFIED };
+                               session.getWorkspace().getObservationManager().addEventListener(this,
+                                               Event.NODE_ADDED | Event.NODE_REMOVED | Event.PROPERTY_CHANGED, "/", true, null, nodeTypes,
+                                               true);
+                               while (save()) {
+
+                               }
+
+                       } catch (RepositoryException e1) {
+                               // TODO Auto-generated catch block
+                               e1.printStackTrace();
+                       } finally {
+                               JcrUtils.logoutQuietly(session);
+                       }
+               }
+
+               protected synchronized boolean save() {
+                       try {
+                               wait(100);
+                       } catch (InterruptedException e) {
+                               // silent
+                       }
+                       if (!session.isLive())
+                               return false;
+                       try {
+                               if (session.hasPendingChanges())
+                                       session.save();
+                       } catch (RepositoryException e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       }
+                       return true;
+               }
+
+               @Override
+               public synchronized void onEvent(EventIterator events) {
+                       events: while (events.hasNext()) {
+                               Event event = events.nextEvent();
+//                             if (log.isDebugEnabled())
+//                                     log.debug(event);
+                               try {
+                                       if (event.getType() == Event.NODE_ADDED) {
+                                               Node node = session.getNode(event.getPath());
+                                               if (node.getParent().isNodeType(NodeType.NT_FILE)) {
+//                                                     Node contentNode = node.getNode(Node.JCR_CONTENT);
+                                                       node.addMixin(NodeType.MIX_LAST_MODIFIED);
+                                                       Property property = node.getProperty(Property.JCR_DATA);
+                                                       String etag = toEtag(property.getValue());
+                                                       node.setProperty(JCR_ETAG, etag);
+                                                       setLastModified(node, event);
+//                                             node.getSession().save();
+                                                       if (log.isDebugEnabled())
+                                                               log.debug("Node " + node.getPath() + ": " + event);
+                                               }
+                                       } else if (event.getType() == Event.NODE_REMOVED) {
+                                               String parentPath = JcrUtils.parentPath(event.getPath());
+                                               try {
+                                                       Node parent = session.getNode(parentPath);
+                                                       setLastModified(parent, event);
+
+                                                       if (log.isDebugEnabled())
+                                                               log.debug("Node removed from " + parent.getPath() + ": " + event);
+                                               } catch (ItemNotFoundException | PathNotFoundException e) {
+                                                       continue events;
+                                               }
+                                       } else if (event.getType() == Event.PROPERTY_CHANGED) {
+                                               Property property = session.getProperty(event.getPath());
+                                               if (property.getName().equals("jcr:lastModified"))
+                                                       continue events;
+                                               if (property.getType() == PropertyType.BINARY && property.getName().equals("jcr:data")
+                                                               && property.getParent().isNodeType(NodeType.NT_UNSTRUCTURED)) {
+                                                       String etag = toEtag(property.getValue());
+                                                       property.getParent().setProperty(JCR_ETAG, etag);
+                                               }
+                                               setLastModified(property.getParent(), event);
+//                                             property.getParent().getSession().save();
+                                               if (log.isDebugEnabled())
+                                                       log.debug("Property " + property.getPath() + ": " + event);
+                                       }
+                               } catch (Exception e) {
+                                       // TODO Auto-generated catch block
+                                       e.printStackTrace();
+                               }
+                       }
+                       notifyAll();
+
+               }
+
+       }
+}
index d4bf4381ed1efd88149ea1bb00c44df680f51164..1098c4dee5d6a8d781e9a50f6a0dc2db2d12f21c 100644 (file)
@@ -99,7 +99,7 @@ public abstract class JcrRepositoryWrapper implements Repository {
                        throws LoginException, NoSuchWorkspaceException, RepositoryException {
                Session session;
                try {
-                       session = getRepository().login(credentials, workspaceName);
+                       session = getRepository(workspaceName).login(credentials, workspaceName);
                } catch (NoSuchWorkspaceException e) {
                        if (autocreateWorkspaces && workspaceName != null)
                                session = createWorkspaceAndLogsIn(credentials, workspaceName);
@@ -126,17 +126,24 @@ public abstract class JcrRepositoryWrapper implements Repository {
        protected void processNewSession(Session session, String workspaceName) {
        }
 
-       /** Wraps access to the repository, making sure it is available. */
+       /**
+        * Wraps access to the repository, making sure it is available.
+        * 
+        * @deprecated Use {@link #getDefaultRepository()} instead.
+        */
+       @Deprecated
        protected synchronized Repository getRepository() {
-               // if (repository == null) {
-               // throw new ArgeoJcrException("No repository initialized."
-               // + " Was the init() method called?"
-               // + " The destroy() method should also"
-               // + " be called on shutdown.");
-               // }
+               return getDefaultRepository();
+       }
+
+       protected synchronized Repository getDefaultRepository() {
                return repository;
        }
 
+       protected synchronized Repository getRepository(String workspaceName) {
+               return getDefaultRepository();
+       }
+
        /**
         * Logs in to the default workspace, creates the required workspace, logs out,
         * logs in to the required workspace.
@@ -145,10 +152,10 @@ public abstract class JcrRepositoryWrapper implements Repository {
                        throws RepositoryException {
                if (workspaceName == null)
                        throw new ArgeoJcrException("No workspace specified.");
-               Session session = getRepository().login(credentials);
+               Session session = getRepository(workspaceName).login(credentials);
                session.getWorkspace().createWorkspace(workspaceName);
                session.logout();
-               return getRepository().login(credentials, workspaceName);
+               return getRepository(workspaceName).login(credentials, workspaceName);
        }
 
        public boolean isStandardDescriptor(String key) {
diff --git a/org.argeo.maintenance/src/org/argeo/maintenance/backup/BackupContentHandler.java b/org.argeo.maintenance/src/org/argeo/maintenance/backup/BackupContentHandler.java
new file mode 100644 (file)
index 0000000..0993238
--- /dev/null
@@ -0,0 +1,195 @@
+package org.argeo.maintenance.backup;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Writer;
+import java.util.Base64;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.jcr.Binary;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.io.IOUtils;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+public class BackupContentHandler extends DefaultHandler {
+       final static int MAX_DEPTH = 1024;
+       final static String SV_NAMESPACE_URI = "http://www.jcp.org/jcr/sv/1.0";
+       // elements
+       final static String NODE = "node";
+       final static String PROPERTY = "property";
+       final static String VALUE = "value";
+       // attributes
+       final static String NAME = "name";
+       final static String MULTIPLE = "multiple";
+       final static String TYPE = "type";
+
+       // values
+       final static String BINARY = "Binary";
+       final static String JCR_CONTENT = "jcr:content";
+
+       private Writer out;
+       private Session session;
+       private Set<String> contentPaths = new TreeSet<>();
+
+       public BackupContentHandler(Writer out, Session session) {
+               super();
+               this.out = out;
+               this.session = session;
+       }
+
+       private int currentDepth = -1;
+       private String[] currentPath = new String[MAX_DEPTH];
+
+       private boolean currentPropertyIsMultiple = false;
+       private String currentEncoded = null;
+       private Base64.Encoder base64encore = Base64.getEncoder();
+
+       @Override
+       public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+               boolean isNode;
+               boolean isProperty;
+               switch (localName) {
+               case NODE:
+                       isNode = true;
+                       isProperty = false;
+                       break;
+               case PROPERTY:
+                       isNode = false;
+                       isProperty = true;
+                       break;
+               default:
+                       isNode = false;
+                       isProperty = false;
+               }
+
+               if (isNode) {
+                       String nodeName = attributes.getValue(SV_NAMESPACE_URI, NAME);
+                       currentDepth = currentDepth + 1;
+                       if (currentDepth > 0)
+                               currentPath[currentDepth - 1] = nodeName;
+//                     System.out.println(getCurrentPath() + " , depth=" + currentDepth);
+               }
+
+               if (SV_NAMESPACE_URI.equals(uri))
+                       try {
+                               out.write("<");
+                               out.write(localName);
+                               if (isProperty)
+                                       currentPropertyIsMultiple = false; // always reset
+                               for (int i = 0; i < attributes.getLength(); i++) {
+                                       String ns = attributes.getURI(i);
+                                       if (SV_NAMESPACE_URI.equals(ns)) {
+                                               String attrName = attributes.getLocalName(i);
+                                               String attrValue = attributes.getValue(i);
+                                               out.write(" ");
+                                               out.write(attrName);
+                                               out.write("=");
+                                               out.write("\"");
+                                               out.write(attrValue);
+                                               out.write("\"");
+                                               if (isProperty) {
+                                                       if (MULTIPLE.equals(attrName))
+                                                               currentPropertyIsMultiple = Boolean.parseBoolean(attrValue);
+                                                       else if (TYPE.equals(attrName)) {
+                                                               if (BINARY.equals(attrValue)) {
+                                                                       if (JCR_CONTENT.equals(getCurrentName())) {
+                                                                               contentPaths.add(getCurrentPath());
+                                                                       } else {
+                                                                               Binary binary = session.getNode(getCurrentPath()).getProperty(attrName)
+                                                                                               .getBinary();
+                                                                               try (InputStream in = binary.getStream()) {
+                                                                                       currentEncoded = base64encore.encodeToString(IOUtils.toByteArray(in));
+                                                                               } finally {
+
+                                                                               }
+                                                                       }
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               }
+                               if (currentDepth == 0) {
+                                       out.write(" xmlns=\"" + SV_NAMESPACE_URI + "\"");
+                               }
+                               out.write(">");
+                               if (isNode)
+                                       out.write("\n");
+                               else if (isProperty && currentPropertyIsMultiple)
+                                       out.write("\n");
+                       } catch (IOException | RepositoryException e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       }
+       }
+
+       @Override
+       public void endElement(String uri, String localName, String qName) throws SAXException {
+               if (localName.equals(NODE)) {
+//                     System.out.println("endElement " + getCurrentPath() + " , depth=" + currentDepth);
+                       if (currentDepth > 0)
+                               currentPath[currentDepth - 1] = null;
+                       currentDepth = currentDepth - 1;
+               }
+               boolean isValue = localName.equals(VALUE);
+               if (SV_NAMESPACE_URI.equals(uri))
+                       try {
+                               if (isValue && currentEncoded != null) {
+                                       out.write(currentEncoded);
+                               }
+                               currentEncoded = null;
+                               out.write("</");
+                               out.write(localName);
+                               out.write(">");
+                               if (!isValue)
+                                       out.write("\n");
+                               else {
+                                       if (currentPropertyIsMultiple)
+                                               out.write("\n");
+                               }
+                       } catch (IOException e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       }
+       }
+
+       @Override
+       public void characters(char[] ch, int start, int length) throws SAXException {
+               try {
+                       out.write(ch, start, length);
+               } catch (IOException e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               }
+       }
+
+       protected String getCurrentName() {
+               assert currentDepth >= 0;
+               if (currentDepth == 0)
+                       return "jcr:root";
+               return currentPath[currentDepth - 1];
+       }
+
+       protected String getCurrentPath() {
+               if (currentDepth == 0)
+                       return "/";
+               StringBuilder sb = new StringBuilder("/");
+               for (int i = 0; i < currentDepth; i++) {
+                       if (i != 0)
+                               sb.append('/');
+                       sb.append(currentPath[i]);
+               }
+               return sb.toString();
+       }
+
+       public Set<String> getContentPaths() {
+               return contentPaths;
+       }
+
+       
+       
+}
diff --git a/org.argeo.maintenance/src/org/argeo/maintenance/backup/LogicalBackup.java b/org.argeo.maintenance/src/org/argeo/maintenance/backup/LogicalBackup.java
new file mode 100644 (file)
index 0000000..d1b31b3
--- /dev/null
@@ -0,0 +1,223 @@
+package org.argeo.maintenance.backup;
+
+import java.io.BufferedWriter;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipOutputStream;
+
+import javax.jcr.Binary;
+import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.Property;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.node.NodeUtils;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.xml.sax.SAXException;
+
+public class LogicalBackup {
+       private final static Log log = LogFactory.getLog(LogicalBackup.class);
+
+       public final static String WORKSPACES_BASE = "workspaces/";
+       public final static String OSGI_BASE = "share/osgi/";
+       private final Repository repository;
+       private final BundleContext bundleContext;
+
+       private final ZipOutputStream zout;
+       private final Path basePath;
+
+       public LogicalBackup(BundleContext bundleContext, Repository repository, ZipOutputStream zout) {
+               this.repository = repository;
+               this.zout = zout;
+               this.basePath = null;
+               this.bundleContext = bundleContext;
+       }
+
+       public LogicalBackup(BundleContext bundleContext, Repository repository, Path basePath) {
+               this.repository = repository;
+               this.zout = null;
+               this.basePath = basePath;
+               this.bundleContext = bundleContext;
+       }
+
+       public void perform() throws RepositoryException, IOException {
+               for (Bundle bundle : bundleContext.getBundles()) {
+                       String relativePath = OSGI_BASE + "boot/" + bundle.getSymbolicName() + ".jar";
+                       Dictionary<String, String> headers = bundle.getHeaders();
+                       Manifest manifest = new Manifest();
+                       Enumeration<String> headerKeys = headers.keys();
+                       while (headerKeys.hasMoreElements()) {
+                               String headerKey = headerKeys.nextElement();
+                               String headerValue = headers.get(headerKey);
+                               manifest.getMainAttributes().putValue(headerKey, headerValue);
+                       }
+                       try (JarOutputStream jarOut = new JarOutputStream(openOutputStream(relativePath), manifest)) {
+//                             Enumeration<String> entryPaths = bundle.getEntryPaths("/");
+//                             while (entryPaths.hasMoreElements()) {
+//                                     String entryPath = entryPaths.nextElement();
+//                                     ZipEntry entry = new ZipEntry(entryPath);
+//                                     URL entryUrl = bundle.getEntry(entryPath);
+//                                     try (InputStream in = entryUrl.openStream()) {
+//                                             jarOut.putNextEntry(entry);
+//                                             IOUtils.copy(in, jarOut);
+//                                             jarOut.closeEntry();
+//                                     } catch (FileNotFoundException e) {
+//                                             log.warn(entryPath);
+//                                     }
+//                             }
+                               Enumeration<URL> resourcePaths = bundle.findEntries("/", "*", true);
+                               resources: while (resourcePaths.hasMoreElements()) {
+                                       URL entryUrl = resourcePaths.nextElement();
+                                       String entryPath = entryUrl.getPath();
+                                       if (entryPath.equals(""))
+                                               continue resources;
+                                       if (entryPath.endsWith("/"))
+                                               continue resources;
+                                       String entryName = entryPath.substring(1);// remove first '/'
+                                       if (entryUrl.getPath().equals("/META-INF/"))
+                                               continue resources;
+                                       if (entryUrl.getPath().equals("/META-INF/MANIFEST.MF"))
+                                               continue resources;
+                                       // dev
+                                       if (entryUrl.getPath().startsWith("/target"))
+                                               continue resources;
+                                       if (entryUrl.getPath().startsWith("/src"))
+                                               continue resources;
+                                       if (entryUrl.getPath().startsWith("/ext"))
+                                               continue resources;
+
+                                       if (entryName.startsWith("bin/")) {// dev
+                                               entryName = entryName.substring("bin/".length());
+                                       }
+
+                                       ZipEntry entry = new ZipEntry(entryName);
+                                       try (InputStream in = entryUrl.openStream()) {
+                                               try {
+                                                       jarOut.putNextEntry(entry);
+                                               } catch (ZipException e) {// duplicate
+                                                       continue resources;
+                                               }
+                                               IOUtils.copy(in, jarOut);
+                                               jarOut.closeEntry();
+//                                             log.info(entryUrl);
+                                       } catch (FileNotFoundException e) {
+                                               log.warn(entryUrl + ": " + e.getMessage());
+                                       }
+                               }
+                       }
+               }
+
+               Session defaultSession = login(null);
+               try {
+                       String[] workspaceNames = defaultSession.getWorkspace().getAccessibleWorkspaceNames();
+                       workspaces: for (String workspaceName : workspaceNames) {
+                               if ("security".equals(workspaceName))
+                                       continue workspaces;
+                               perform(workspaceName);
+                       }
+               } finally {
+                       JcrUtils.logoutQuietly(defaultSession);
+               }
+
+       }
+
+       public void perform(String workspaceName) throws RepositoryException, IOException {
+               Session session = login(workspaceName);
+               try {
+                       String relativePath = WORKSPACES_BASE + workspaceName + ".xml";
+                       OutputStream xmlOut = openOutputStream(relativePath);
+                       BackupContentHandler contentHandler;
+                       try (Writer writer = new BufferedWriter(new OutputStreamWriter(xmlOut, StandardCharsets.UTF_8))) {
+                               contentHandler = new BackupContentHandler(writer, session);
+                               try {
+                                       session.exportSystemView("/", contentHandler, true, false);
+                                       if (log.isDebugEnabled())
+                                               log.debug("Workspace " + workspaceName + ": metadata exported to " + relativePath);
+                               } catch (PathNotFoundException e) {
+                                       // TODO Auto-generated catch block
+                                       e.printStackTrace();
+                               } catch (SAXException e) {
+                                       // TODO Auto-generated catch block
+                                       e.printStackTrace();
+                               } catch (RepositoryException e) {
+                                       // TODO Auto-generated catch block
+                                       e.printStackTrace();
+                               }
+                       }
+                       for (String path : contentHandler.getContentPaths()) {
+                               Node contentNode = session.getNode(path);
+                               Binary binary = contentNode.getProperty(Property.JCR_DATA).getBinary();
+                               String fileRelativePath = WORKSPACES_BASE + workspaceName + contentNode.getParent().getPath();
+                               try (InputStream in = binary.getStream(); OutputStream out = openOutputStream(fileRelativePath)) {
+                                       IOUtils.copy(in, out);
+                                       if (log.isDebugEnabled())
+                                               log.debug("Workspace " + workspaceName + ": file content exported to " + fileRelativePath);
+                               } finally {
+
+                               }
+
+                       }
+
+//                     OutputStream xmlOut = openOutputStream(relativePath);
+//                     try {
+//                             session.exportSystemView("/", xmlOut, false, false);
+//                     } finally {
+//                             closeOutputStream(relativePath, xmlOut);
+//                     }
+
+                       // TODO scan all binaries
+               } finally {
+                       JcrUtils.logoutQuietly(session);
+               }
+       }
+
+       protected OutputStream openOutputStream(String relativePath) throws IOException {
+               if (zout != null) {
+                       ZipEntry entry = new ZipEntry(relativePath);
+                       zout.putNextEntry(entry);
+                       return zout;
+               } else if (basePath != null) {
+                       Path targetPath = basePath.resolve(Paths.get(relativePath));
+                       Files.createDirectories(targetPath.getParent());
+                       return Files.newOutputStream(targetPath);
+               } else {
+                       throw new UnsupportedOperationException();
+               }
+       }
+
+       protected void closeOutputStream(String relativePath, OutputStream out) throws IOException {
+               if (zout != null) {
+                       zout.closeEntry();
+               } else if (basePath != null) {
+                       out.close();
+               } else {
+                       throw new UnsupportedOperationException();
+               }
+       }
+
+       protected Session login(String workspaceName) {
+               return NodeUtils.openDataAdminSession(repository, workspaceName);
+       }
+}
diff --git a/org.argeo.maintenance/src/org/argeo/maintenance/internal/Activator.java b/org.argeo.maintenance/src/org/argeo/maintenance/internal/Activator.java
new file mode 100644 (file)
index 0000000..be001a3
--- /dev/null
@@ -0,0 +1,34 @@
+package org.argeo.maintenance.internal;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import javax.jcr.Repository;
+
+import org.argeo.maintenance.backup.LogicalBackup;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+public class Activator implements BundleActivator {
+
+       @Override
+       public void start(BundleContext context) throws Exception {
+               try {
+                       Repository repository = context.getService(context.getServiceReference(Repository.class));
+                       Path basePath = Paths.get(System.getProperty("user.dir"), "backup");
+                       LogicalBackup backup = new LogicalBackup(context, repository, basePath);
+                       backup.perform();
+               } catch (Exception e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               }
+
+       }
+
+       @Override
+       public void stop(BundleContext context) throws Exception {
+               // TODO Auto-generated method stub
+
+       }
+
+}
index 09e3bc9b241c477ea341af9ee029becad0c2148c..0d2e1e32e5dde5a026a36de5f4cd6c0b1b01432d 100644 (file)
@@ -1,2 +1,6 @@
 /bin/
 /target/
+/configuration/
+/state/
+/data/
+/log4j.properties
diff --git a/org.argeo.osgi.boot/src/org/argeo/osgi/boot/internal/springutil/Bootstrap.java b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/internal/springutil/Bootstrap.java
new file mode 100644 (file)
index 0000000..2ac4562
--- /dev/null
@@ -0,0 +1,101 @@
+package org.argeo.osgi.boot.internal.springutil;
+
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class Bootstrap {
+
+       public static void main(String[] args) {
+               try {
+                       String configurationArea = "file:" + System.getProperty("user.dir") + "/state";
+                       String instanceArea = "file:" + System.getProperty("user.dir") + "/data";
+                       String log4jUrl = "file:" + System.getProperty("user.dir") + "/log4j.properties";
+
+                       System.setProperty("org.osgi.service.http.port", "7070");
+                       System.setProperty("log4j.configuration", log4jUrl);
+
+                       System.setProperty("osgi.console", "2323");
+                       Map<String, String> props = new HashMap<String, String>();
+                       props.put("osgi.clean", "true");
+//                     props.put("osgi.console", "2323");
+                       props.put("osgi.configuration.area", configurationArea);
+                       props.put("osgi.instance.area", instanceArea);
+
+                       System.setProperty("argeo.osgi.start.2.node",
+                                       "org.eclipse.equinox.console,org.eclipse.equinox.http.servlet,org.eclipse.equinox.ds,"
+                                                       + "org.eclipse.equinox.metatype,org.eclipse.equinox.cm,org.eclipse.rap.rwt.osgi");
+                       System.setProperty("argeo.osgi.start.3.node", "org.argeo.cms");
+
+                       // URL osgiJar =
+                       // Bootstrap.class.getClassLoader().getResource("/usr/share/osgi/boot/org.eclipse.org.jar");
+                       URL osgiJar = new URL(
+                                       "file:///home/mbaudier/dev/git/apache2/argeo-commons/demo/exec/cms-e4-rap/backup/share/osgi/boot/org.eclipse.org.jar");
+                       URL osgiBootJar = new URL(
+                                       "file:///home/mbaudier/dev/git/apache2/argeo-commons/demo/exec/cms-e4-rap/backup/share/osgi/boot/org.argeo.osgi.boot.jar");
+                       URL[] jarUrls = { osgiJar };
+                       try (URLClassLoader urlCl = new URLClassLoader(jarUrls)) {
+
+                               // Class<?> factoryClass =
+                               // urlCl.loadClass("/org/eclipse/osgi/launch/EquinoxFactory");
+                               Class<?> factoryClass = urlCl.loadClass("org.eclipse.osgi.launch.EquinoxFactory");
+                               Class<?> frameworkClass = urlCl.loadClass("org.osgi.framework.launch.Framework");
+                               Class<?> bundleContextClass = urlCl.loadClass("org.osgi.framework.BundleContext");
+                               Class<?> bundleClass = urlCl.loadClass("org.osgi.framework.Bundle");
+
+                               Object factory = factoryClass.getConstructor().newInstance();
+                               Method newFrameworkMethod = factoryClass.getMethod("newFramework", Map.class);
+                               Object framework = newFrameworkMethod.invoke(factory, props);
+                               Method startFramework = frameworkClass.getMethod("start", new Class[] {});
+                               startFramework.invoke(framework);
+                               Method getBundleContext = frameworkClass.getMethod("getBundleContext", new Class[] {});
+                               Object bundleContext = getBundleContext.invoke(framework);
+                               Class<?>[] installArgs = { String.class, InputStream.class };
+                               Method install = bundleContextClass.getMethod("installBundle", installArgs);
+                               Method startBundle = bundleClass.getMethod("start");
+                               Method getSymbolicName = bundleClass.getMethod("getSymbolicName");
+
+                               Path basePath = Paths.get(
+                                               "/home/mbaudier/dev/git/apache2/argeo-commons/demo/exec/cms-e4-rap/backup/share/osgi/boot/");
+                               List<Object> bundles = new ArrayList<>();
+                               for (Path p : Files.newDirectoryStream(basePath)) {
+                                       try (InputStream in = Files.newInputStream(p)) {
+                                               Object bundle = install.invoke(bundleContext, "file:" + p, in);
+                                               bundles.add(bundle);
+                                               System.out.println("Installed " + bundle);
+                                       } catch (Exception e) {
+                                               if (!p.getFileName().toString().startsWith("org.eclipse.osgi")) {
+                                                       System.err.println(p);
+                                                       e.printStackTrace();
+                                               }
+                                       }
+                               }
+
+//                             for (Object bundle : bundles) {
+//                                     try {
+//                                             String symbolicName = getSymbolicName.invoke(bundle).toString();
+//                                             startBundle.invoke(bundle);
+//                                     } catch (Exception e) {
+//                                             // TODO Auto-generated catch block
+//                                             e.printStackTrace();
+//                                     }
+//                             }
+
+                               Object osgiBootBundle = install.invoke(bundleContext, osgiBootJar.toString(), osgiBootJar.openStream());
+                               startBundle.invoke(osgiBootBundle);
+                       }
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
+
+       }
+
+}