--- /dev/null
+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);
+ }
+}