From 3c7803ca05e2b0276d635e64046d924d3f1884c9 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Wed, 13 Jan 2021 11:45:27 +0100 Subject: [PATCH] Work on logical backups. --- .../cms/internal/kernel/CmsDeployment.java | 13 +++++ .../cms/internal/kernel/KernelUtils.java | 57 ++++++++++++------- .../JackrabbitDataModelMigration.java | 22 ++++--- .../maintenance/MaintenanceException.java | 13 ----- .../backup/BackupContentHandler.java | 48 +++++++++++----- .../maintenance/backup/LogicalBackup.java | 14 ++--- .../maintenance/backup/LogicalRestore.java | 52 +++++++++++++++++ 7 files changed, 155 insertions(+), 64 deletions(-) delete mode 100644 org.argeo.maintenance/src/org/argeo/maintenance/MaintenanceException.java create mode 100644 org.argeo.maintenance/src/org/argeo/maintenance/backup/LogicalRestore.java diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeployment.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeployment.java index 751ff6249..57ce89809 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeployment.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsDeployment.java @@ -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> initRepositorySr; try { @@ -581,6 +593,7 @@ public class CmsDeployment implements NodeDeployment { if (cn != null) { List publishAsLocalRepo = new ArrayList<>(); if (cn.equals(NodeConstants.NODE_REPOSITORY)) { +// JackrabbitDataModelMigration.clearRepositoryCaches(repoContext.getRepositoryConfig()); prepareNodeRepository(repoContext.getRepository(), publishAsLocalRepo); // TODO separate home repository prepareHomeRepository(repoContext.getRepository()); diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java index a5362c64e..7d296ae0e 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java @@ -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() { + + @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() { + return loginContext; + } + + static void doAsDataAdmin(Runnable action) { + LoginContext loginContext = loginAsDataAdmin(); + Subject.doAs(loginContext.getSubject(), new PrivilegedAction() { @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(); } diff --git a/org.argeo.core/src/org/argeo/jackrabbit/JackrabbitDataModelMigration.java b/org.argeo.core/src/org/argeo/jackrabbit/JackrabbitDataModelMigration.java index 838446d5c..9a49a063f 100644 --- a/org.argeo.core/src/org/argeo/jackrabbit/JackrabbitDataModelMigration.java +++ b/org.argeo.core/src/org/argeo/jackrabbit/JackrabbitDataModelMigration.java @@ -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 { -// 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 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(""); 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; } - - } diff --git a/org.argeo.maintenance/src/org/argeo/maintenance/backup/LogicalBackup.java b/org.argeo.maintenance/src/org/argeo/maintenance/backup/LogicalBackup.java index 9964b7f53..864de25be 100644 --- a/org.argeo.maintenance/src/org/argeo/maintenance/backup/LogicalBackup.java +++ b/org.argeo.maintenance/src/org/argeo/maintenance/backup/LogicalBackup.java @@ -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 index 000000000..b430af6e1 --- /dev/null +++ b/org.argeo.maintenance/src/org/argeo/maintenance/backup/LogicalRestore.java @@ -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 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); + } + } + +} -- 2.30.2