From 9e5ea515aa9a855645c9d20c478c14770264a5cb Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Sun, 26 Jan 2020 22:21:00 +0100 Subject: [PATCH] Work on backups and file metadata indexing. --- .../cms/internal/kernel/CmsDeployment.java | 6 +- .../kernel/JackrabbitLocalRepository.java | 201 ++++++++++++++++ .../org/argeo/jcr/JcrRepositoryWrapper.java | 27 ++- .../backup/BackupContentHandler.java | 195 +++++++++++++++ .../maintenance/backup/LogicalBackup.java | 223 ++++++++++++++++++ .../argeo/maintenance/internal/Activator.java | 34 +++ org.argeo.osgi.boot/.gitignore | 4 + .../boot/internal/springutil/Bootstrap.java | 101 ++++++++ 8 files changed, 780 insertions(+), 11 deletions(-) create mode 100644 org.argeo.cms/src/org/argeo/cms/internal/kernel/JackrabbitLocalRepository.java create mode 100644 org.argeo.maintenance/src/org/argeo/maintenance/backup/BackupContentHandler.java create mode 100644 org.argeo.maintenance/src/org/argeo/maintenance/backup/LogicalBackup.java create mode 100644 org.argeo.maintenance/src/org/argeo/maintenance/internal/Activator.java create mode 100644 org.argeo.osgi.boot/src/org/argeo/osgi/boot/internal/springutil/Bootstrap.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 59d8c910f..f09a00807 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 @@ -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 index 000000000..dc47e6e13 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/kernel/JackrabbitLocalRepository.java @@ -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 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(); + + } + + } +} diff --git a/org.argeo.jcr/src/org/argeo/jcr/JcrRepositoryWrapper.java b/org.argeo.jcr/src/org/argeo/jcr/JcrRepositoryWrapper.java index d4bf4381e..1098c4dee 100644 --- a/org.argeo.jcr/src/org/argeo/jcr/JcrRepositoryWrapper.java +++ b/org.argeo.jcr/src/org/argeo/jcr/JcrRepositoryWrapper.java @@ -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 index 000000000..099323833 --- /dev/null +++ b/org.argeo.maintenance/src/org/argeo/maintenance/backup/BackupContentHandler.java @@ -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 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(""); + 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 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 index 000000000..d1b31b3ec --- /dev/null +++ b/org.argeo.maintenance/src/org/argeo/maintenance/backup/LogicalBackup.java @@ -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 headers = bundle.getHeaders(); + Manifest manifest = new Manifest(); + Enumeration 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 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 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 index 000000000..be001a3af --- /dev/null +++ b/org.argeo.maintenance/src/org/argeo/maintenance/internal/Activator.java @@ -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 + + } + +} diff --git a/org.argeo.osgi.boot/.gitignore b/org.argeo.osgi.boot/.gitignore index 09e3bc9b2..0d2e1e32e 100644 --- a/org.argeo.osgi.boot/.gitignore +++ b/org.argeo.osgi.boot/.gitignore @@ -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 index 000000000..2ac4562c8 --- /dev/null +++ b/org.argeo.osgi.boot/src/org/argeo/osgi/boot/internal/springutil/Bootstrap.java @@ -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 props = new HashMap(); + 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 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(); + } + + } + +} -- 2.30.2