Work on logical backups.
authorMathieu Baudier <mbaudier@argeo.org>
Wed, 13 Jan 2021 10:45:27 +0000 (11:45 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Wed, 13 Jan 2021 10:45:27 +0000 (11:45 +0100)
org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeployment.java
org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java
org.argeo.core/src/org/argeo/jackrabbit/JackrabbitDataModelMigration.java
org.argeo.maintenance/src/org/argeo/maintenance/MaintenanceException.java [deleted file]
org.argeo.maintenance/src/org/argeo/maintenance/backup/BackupContentHandler.java
org.argeo.maintenance/src/org/argeo/maintenance/backup/LogicalBackup.java
org.argeo.maintenance/src/org/argeo/maintenance/backup/LogicalRestore.java [new file with mode: 0644]

index 751ff6249327771eaaf239db5a564ca36669a516..57ce898097c61cfe05c14068e684fd0bad447a3f 100644 (file)
@@ -11,6 +11,7 @@ import java.lang.management.ManagementFactory;
 import java.net.URL;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -47,6 +48,7 @@ import org.argeo.cms.internal.http.HttpUtils;
 import org.argeo.jcr.Jcr;
 import org.argeo.jcr.JcrException;
 import org.argeo.jcr.JcrUtils;
+import org.argeo.maintenance.backup.LogicalRestore;
 import org.argeo.naming.LdapAttrs;
 import org.argeo.osgi.useradmin.UserAdminConf;
 import org.argeo.util.LangUtils;
@@ -292,6 +294,16 @@ public class CmsDeployment implements NodeDeployment {
                // home
                prepareDataModel(NodeConstants.NODE_REPOSITORY, deployedNodeRepository, publishAsLocalRepo);
 
+               // init from backup
+               Path restorePath = Paths.get(System.getProperty("user.dir"), "restore");
+               if (Files.exists(restorePath)) {
+                       if (log.isDebugEnabled())
+                               log.debug("Found backup " + restorePath + ", restoring it...");
+                       LogicalRestore logicalRestore = new LogicalRestore(bc, deployedNodeRepository, restorePath);
+                       KernelUtils.doAsDataAdmin(logicalRestore);
+                       log.info("Restored backup from " + restorePath);
+               }
+
                // init from repository
                Collection<ServiceReference<Repository>> initRepositorySr;
                try {
@@ -581,6 +593,7 @@ public class CmsDeployment implements NodeDeployment {
                        if (cn != null) {
                                List<String> publishAsLocalRepo = new ArrayList<>();
                                if (cn.equals(NodeConstants.NODE_REPOSITORY)) {
+//                                     JackrabbitDataModelMigration.clearRepositoryCaches(repoContext.getRepositoryConfig());
                                        prepareNodeRepository(repoContext.getRepository(), publishAsLocalRepo);
                                        // TODO separate home repository
                                        prepareHomeRepository(repoContext.getRepository());
index a5362c64e7484686ff64a0c2631d84acdc06b1cd..7d296ae0e91e8b1dfbe9dc7d5c8234d9de803be6 100644 (file)
@@ -26,8 +26,6 @@ import javax.security.auth.login.LoginException;
 import org.apache.commons.logging.Log;
 import org.argeo.api.DataModelNamespace;
 import org.argeo.api.NodeConstants;
-import org.argeo.cms.CmsException;
-import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.util.tracker.ServiceTracker;
 
@@ -160,6 +158,28 @@ class KernelUtils implements KernelConstants {
        }
 
        static Session openAdminSession(final Repository repository, final String workspaceName) {
+               LoginContext loginContext = loginAsDataAdmin();
+               return Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Session>() {
+
+                       @Override
+                       public Session run() {
+                               try {
+                                       return repository.login(workspaceName);
+                               } catch (RepositoryException e) {
+                                       throw new IllegalStateException("Cannot open admin session", e);
+                               } finally {
+                                       try {
+                                               loginContext.logout();
+                                       } catch (LoginException e) {
+                                               throw new IllegalStateException(e);
+                                       }
+                               }
+                       }
+
+               });
+       }
+
+       static LoginContext loginAsDataAdmin() {
                ClassLoader currentCl = Thread.currentThread().getContextClassLoader();
                Thread.currentThread().setContextClassLoader(KernelUtils.class.getClassLoader());
                LoginContext loginContext;
@@ -171,14 +191,24 @@ class KernelUtils implements KernelConstants {
                } finally {
                        Thread.currentThread().setContextClassLoader(currentCl);
                }
-               return Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Session>() {
+               return loginContext;
+       }
+
+       static void doAsDataAdmin(Runnable action) {
+               LoginContext loginContext = loginAsDataAdmin();
+               Subject.doAs(loginContext.getSubject(), new PrivilegedAction<Void>() {
 
                        @Override
-                       public Session run() {
+                       public Void run() {
                                try {
-                                       return repository.login(workspaceName);
-                               } catch (RepositoryException e) {
-                                       throw new IllegalStateException("Cannot open admin session", e);
+                                       action.run();
+                                       return null;
+                               } finally {
+                                       try {
+                                               loginContext.logout();
+                                       } catch (LoginException e) {
+                                               throw new IllegalStateException(e);
+                                       }
                                }
                        }
 
@@ -197,19 +227,6 @@ class KernelUtils implements KernelConstants {
 //             new Thread(run, "Open service tracker " + st).start();
        }
 
-       /**
-        * @return the {@link BundleContext} of the {@link Bundle} which provided this
-        *         class, never null.
-        * @throws CmsException if the related bundle is not active
-        */
-//     static BundleContext getBundleContext(Class<?> clzz) {
-////           Bundle bundle = FrameworkUtil.getBundle(clzz);
-//             BundleContext bc = Activator.getBundleContext();
-//             if (bc == null)
-//                     throw new CmsException("Bundle " + bundle.getSymbolicName() + " is not active");
-//             return bc;
-//     }
-
        static BundleContext getBundleContext() {
                return Activator.getBundleContext();
        }
index 838446d5cb355fe3c491b54d36f340dfcb0eb42d..9a49a063ffc08df6ff93a904ae1843816c0bbd38 100644 (file)
@@ -1,5 +1,6 @@
 package org.argeo.jackrabbit;
 
+import java.awt.geom.CubicCurve2D;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.Reader;
@@ -9,17 +10,20 @@ 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.apache.jackrabbit.commons.cnd.CndImporter;
 import org.apache.jackrabbit.commons.cnd.ParseException;
 import org.apache.jackrabbit.core.config.RepositoryConfig;
-import org.argeo.jcr.JcrException;
+import org.apache.jackrabbit.core.fs.FileSystemException;
 import org.argeo.jcr.JcrCallback;
+import org.argeo.jcr.JcrException;
 import org.argeo.jcr.JcrUtils;
 
 /** Migrate the data in a Jackrabbit repository. */
 @Deprecated
 public class JackrabbitDataModelMigration implements Comparable<JackrabbitDataModelMigration> {
-//     private final static Log log = LogFactory.getLog(JackrabbitDataModelMigration.class);
+       private final static Log log = LogFactory.getLog(JackrabbitDataModelMigration.class);
 
        private String dataModelNodePath;
        private String targetVersion;
@@ -73,8 +77,8 @@ public class JackrabbitDataModelMigration implements Comparable<JackrabbitDataMo
                        return true;
                } catch (RepositoryException e) {
                        JcrUtils.discardQuietly(session);
-                       throw new JcrException(
-                                       "Migration of data model " + dataModelNodePath + " to " + targetVersion + " failed.", e);
+                       throw new JcrException("Migration of data model " + dataModelNodePath + " to " + targetVersion + " failed.",
+                                       e);
                } catch (ParseException | IOException e) {
                        JcrUtils.discardQuietly(session);
                        throw new RuntimeException(
@@ -95,11 +99,15 @@ public class JackrabbitDataModelMigration implements Comparable<JackrabbitDataMo
                try {
                        String customeNodeTypesPath = "/nodetypes/custom_nodetypes.xml";
                        // FIXME causes weird error in Eclipse
-//                      repositoryConfig.getFileSystem().deleteFile(customeNodeTypesPath);
-//                     if (log.isDebugEnabled())
-//                             log.debug("Cleared " + customeNodeTypesPath);
+                       repositoryConfig.getFileSystem().deleteFile(customeNodeTypesPath);
+                       if (log.isDebugEnabled())
+                               log.debug("Cleared " + customeNodeTypesPath);
                } catch (RuntimeException e) {
                        throw e;
+               } catch (RepositoryException e) {
+                       throw new JcrException(e);
+               } catch (FileSystemException e) {
+                       throw new RuntimeException("Cannot clear node types cache.",e);
                }
 
                // File customNodeTypes = new File(home.getPath()
diff --git a/org.argeo.maintenance/src/org/argeo/maintenance/MaintenanceException.java b/org.argeo.maintenance/src/org/argeo/maintenance/MaintenanceException.java
deleted file mode 100644 (file)
index dc4243e..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-package org.argeo.maintenance;
-
-public class MaintenanceException extends RuntimeException {
-       private static final long serialVersionUID = -4571088120514827735L;
-
-       public MaintenanceException(String message) {
-               super(message);
-       }
-
-       public MaintenanceException(String message, Throwable cause) {
-               super(message, cause);
-       }
-}
index 099323833dfe6da23fdfc07a641428ccbf5d15d6..745d39d1d36f9ad22fabf44bd5d2e8c41d6883bb 100644 (file)
@@ -12,13 +12,16 @@ import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 
 import org.apache.commons.io.IOUtils;
+import org.argeo.jcr.JcrException;
 import org.xml.sax.Attributes;
 import org.xml.sax.SAXException;
 import org.xml.sax.helpers.DefaultHandler;
 
+/** XML handler serialising a JCR system view. */
 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";
+       final static String SV_PREFIX = "sv";
        // elements
        final static String NODE = "node";
        final static String PROPERTY = "property";
@@ -36,6 +39,8 @@ public class BackupContentHandler extends DefaultHandler {
        private Session session;
        private Set<String> contentPaths = new TreeSet<>();
 
+       private boolean inSystem = false;
+
        public BackupContentHandler(Writer out, Session session) {
                super();
                this.out = out;
@@ -73,12 +78,17 @@ public class BackupContentHandler extends DefaultHandler {
                        if (currentDepth > 0)
                                currentPath[currentDepth - 1] = nodeName;
 //                     System.out.println(getCurrentPath() + " , depth=" + currentDepth);
+                       if ("jcr:system".equals(nodeName)) {
+                               inSystem = true;
+                       }
                }
+               if (inSystem)
+                       return;
 
                if (SV_NAMESPACE_URI.equals(uri))
                        try {
                                out.write("<");
-                               out.write(localName);
+                               out.write(SV_PREFIX + ":" + localName);
                                if (isProperty)
                                        currentPropertyIsMultiple = false; // always reset
                                for (int i = 0; i < attributes.getLength(); i++) {
@@ -87,7 +97,7 @@ public class BackupContentHandler extends DefaultHandler {
                                                String attrName = attributes.getLocalName(i);
                                                String attrValue = attributes.getValue(i);
                                                out.write(" ");
-                                               out.write(attrName);
+                                               out.write(SV_PREFIX + ":" + attrName);
                                                out.write("=");
                                                out.write("\"");
                                                out.write(attrValue);
@@ -113,17 +123,19 @@ public class BackupContentHandler extends DefaultHandler {
                                                }
                                        }
                                }
-                               if (currentDepth == 0) {
-                                       out.write(" xmlns=\"" + SV_NAMESPACE_URI + "\"");
+                               if (isNode && currentDepth == 0) {
+                                       // out.write(" xmlns=\"" + SV_NAMESPACE_URI + "\"");
+                                       out.write(" xmlns:" + SV_PREFIX + "=\"" + 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();
+                       } catch (IOException e) {
+                               throw new RuntimeException(e);
+                       } catch (RepositoryException e) {
+                               throw new JcrException(e);
                        }
        }
 
@@ -134,7 +146,17 @@ public class BackupContentHandler extends DefaultHandler {
                        if (currentDepth > 0)
                                currentPath[currentDepth - 1] = null;
                        currentDepth = currentDepth - 1;
+                       if (inSystem) {
+                               // System.out.println("Skip " + getCurrentPath()+" ,
+                               // currentDepth="+currentDepth);
+                               if (currentDepth == 0) {
+                                       inSystem = false;
+                                       return;
+                               }
+                       }
                }
+               if (inSystem)
+                       return;
                boolean isValue = localName.equals(VALUE);
                if (SV_NAMESPACE_URI.equals(uri))
                        try {
@@ -143,7 +165,7 @@ public class BackupContentHandler extends DefaultHandler {
                                }
                                currentEncoded = null;
                                out.write("</");
-                               out.write(localName);
+                               out.write(SV_PREFIX + ":" + localName);
                                out.write(">");
                                if (!isValue)
                                        out.write("\n");
@@ -152,18 +174,18 @@ public class BackupContentHandler extends DefaultHandler {
                                                out.write("\n");
                                }
                        } catch (IOException e) {
-                               // TODO Auto-generated catch block
-                               e.printStackTrace();
+                               throw new RuntimeException(e);
                        }
        }
 
        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {
+               if (inSystem)
+                       return;
                try {
                        out.write(ch, start, length);
                } catch (IOException e) {
-                       // TODO Auto-generated catch block
-                       e.printStackTrace();
+                       throw new RuntimeException(e);
                }
        }
 
@@ -190,6 +212,4 @@ public class BackupContentHandler extends DefaultHandler {
                return contentPaths;
        }
 
-       
-       
 }
index 9964b7f53cbf0e1b96a016095cf06f5294279ecd..864de25be9b3fa4483bb9aee8837f15149a0c664 100644 (file)
@@ -25,7 +25,6 @@ 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;
@@ -188,15 +187,10 @@ public class LogicalBackup implements Runnable {
                                        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();
+                                       throw new RuntimeException("Cannot perform backup of workspace " + workspaceName, e);
                                } catch (RepositoryException e) {
-                                       // TODO Auto-generated catch block
-                                       e.printStackTrace();
+                                       throw new JcrException("Cannot perform backup of workspace " + workspaceName, e);
                                }
                        }
                        for (String path : contentHandler.getContentPaths()) {
@@ -205,8 +199,8 @@ public class LogicalBackup implements Runnable {
                                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);
+                                       if (log.isTraceEnabled())
+                                               log.trace("Workspace " + workspaceName + ": file content exported to " + fileRelativePath);
                                } finally {
 
                                }
diff --git a/org.argeo.maintenance/src/org/argeo/maintenance/backup/LogicalRestore.java b/org.argeo.maintenance/src/org/argeo/maintenance/backup/LogicalRestore.java
new file mode 100644 (file)
index 0000000..b430af6
--- /dev/null
@@ -0,0 +1,52 @@
+package org.argeo.maintenance.backup;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import javax.jcr.ImportUUIDBehavior;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.io.FilenameUtils;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+import org.osgi.framework.BundleContext;
+
+/** Restores a backup in the format defined by {@link LogicalBackup}. */
+public class LogicalRestore implements Runnable {
+       private final Repository repository;
+       private final BundleContext bundleContext;
+       private final Path basePath;
+
+       public LogicalRestore(BundleContext bundleContext, Repository repository, Path basePath) {
+               this.repository = repository;
+               this.basePath = basePath;
+               this.bundleContext = bundleContext;
+       }
+
+       @Override
+       public void run() {
+               Path workspaces = basePath.resolve(LogicalBackup.WORKSPACES_BASE);
+               try (DirectoryStream<Path> xmls = Files.newDirectoryStream(workspaces, "*.xml")) {
+                       for (Path workspacePath : xmls) {
+                               String workspaceName = FilenameUtils.getBaseName(workspacePath.getFileName().toString());
+                               Session session = JcrUtils.loginOrCreateWorkspace(repository, workspaceName);
+                               try (InputStream in = Files.newInputStream(workspacePath)) {
+                                       session.getWorkspace().importXML("/", in,
+                                                       ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+                               } finally {
+                                       JcrUtils.logoutQuietly(session);
+                               }
+                       }
+               } catch (IOException e) {
+                       throw new RuntimeException("Cannot restore backup from " + basePath, e);
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot restore backup from " + basePath, e);
+               }
+       }
+
+}