1 package org
.argeo
.maintenance
.backup
;
3 import java
.io
.BufferedWriter
;
4 import java
.io
.FileNotFoundException
;
5 import java
.io
.IOException
;
6 import java
.io
.InputStream
;
7 import java
.io
.OutputStream
;
8 import java
.io
.OutputStreamWriter
;
11 import java
.nio
.charset
.StandardCharsets
;
12 import java
.nio
.file
.Files
;
13 import java
.nio
.file
.Path
;
14 import java
.nio
.file
.Paths
;
15 import java
.util
.Dictionary
;
16 import java
.util
.Enumeration
;
17 import java
.util
.jar
.JarOutputStream
;
18 import java
.util
.jar
.Manifest
;
19 import java
.util
.zip
.ZipEntry
;
20 import java
.util
.zip
.ZipException
;
21 import java
.util
.zip
.ZipOutputStream
;
23 import javax
.jcr
.Binary
;
24 import javax
.jcr
.Node
;
25 import javax
.jcr
.PathNotFoundException
;
26 import javax
.jcr
.Property
;
27 import javax
.jcr
.Repository
;
28 import javax
.jcr
.RepositoryException
;
29 import javax
.jcr
.Session
;
31 import org
.apache
.commons
.io
.IOUtils
;
32 import org
.apache
.commons
.logging
.Log
;
33 import org
.apache
.commons
.logging
.LogFactory
;
34 import org
.argeo
.jcr
.JcrUtils
;
35 import org
.argeo
.node
.NodeUtils
;
36 import org
.osgi
.framework
.Bundle
;
37 import org
.osgi
.framework
.BundleContext
;
38 import org
.xml
.sax
.SAXException
;
41 * Performs a backup of the data based only on programmatic interfaces. Useful
42 * for migration or live backup. Physical backups of the underlying file
43 * systems, databases, LDAP servers, etc. should be performed for disaster
46 public class LogicalBackup
implements Runnable
{
47 private final static Log log
= LogFactory
.getLog(LogicalBackup
.class);
49 public final static String WORKSPACES_BASE
= "workspaces/";
50 public final static String OSGI_BASE
= "share/osgi/";
51 private final Repository repository
;
52 private final BundleContext bundleContext
;
54 private final ZipOutputStream zout
;
55 private final Path basePath
;
57 public LogicalBackup(BundleContext bundleContext
, Repository repository
, Path basePath
) {
58 this.repository
= repository
;
60 this.basePath
= basePath
;
61 this.bundleContext
= bundleContext
;
64 // public LogicalBackup(BundleContext bundleContext, Repository repository, ZipOutputStream zout) {
65 // this.repository = repository;
67 // this.basePath = null;
68 // this.bundleContext = bundleContext;
74 log
.info("Start logical backup to " + basePath
);
76 } catch (Exception e
) {
78 throw new IllegalStateException("Logical backup failed", e
);
83 public void perform() throws RepositoryException
, IOException
{
84 for (Bundle bundle
: bundleContext
.getBundles()) {
85 String relativePath
= OSGI_BASE
+ "boot/" + bundle
.getSymbolicName() + ".jar";
86 Dictionary
<String
, String
> headers
= bundle
.getHeaders();
87 Manifest manifest
= new Manifest();
88 Enumeration
<String
> headerKeys
= headers
.keys();
89 while (headerKeys
.hasMoreElements()) {
90 String headerKey
= headerKeys
.nextElement();
91 String headerValue
= headers
.get(headerKey
);
92 manifest
.getMainAttributes().putValue(headerKey
, headerValue
);
94 try (JarOutputStream jarOut
= new JarOutputStream(openOutputStream(relativePath
), manifest
)) {
95 // Enumeration<String> entryPaths = bundle.getEntryPaths("/");
96 // while (entryPaths.hasMoreElements()) {
97 // String entryPath = entryPaths.nextElement();
98 // ZipEntry entry = new ZipEntry(entryPath);
99 // URL entryUrl = bundle.getEntry(entryPath);
100 // try (InputStream in = entryUrl.openStream()) {
101 // jarOut.putNextEntry(entry);
102 // IOUtils.copy(in, jarOut);
103 // jarOut.closeEntry();
104 // } catch (FileNotFoundException e) {
105 // log.warn(entryPath);
108 Enumeration
<URL
> resourcePaths
= bundle
.findEntries("/", "*", true);
109 resources
: while (resourcePaths
.hasMoreElements()) {
110 URL entryUrl
= resourcePaths
.nextElement();
111 String entryPath
= entryUrl
.getPath();
112 if (entryPath
.equals(""))
114 if (entryPath
.endsWith("/"))
116 String entryName
= entryPath
.substring(1);// remove first '/'
117 if (entryUrl
.getPath().equals("/META-INF/"))
119 if (entryUrl
.getPath().equals("/META-INF/MANIFEST.MF"))
122 if (entryUrl
.getPath().startsWith("/target"))
124 if (entryUrl
.getPath().startsWith("/src"))
126 if (entryUrl
.getPath().startsWith("/ext"))
129 if (entryName
.startsWith("bin/")) {// dev
130 entryName
= entryName
.substring("bin/".length());
133 ZipEntry entry
= new ZipEntry(entryName
);
134 try (InputStream in
= entryUrl
.openStream()) {
136 jarOut
.putNextEntry(entry
);
137 } catch (ZipException e
) {// duplicate
140 IOUtils
.copy(in
, jarOut
);
142 // log.info(entryUrl);
143 } catch (FileNotFoundException e
) {
144 log
.warn(entryUrl
+ ": " + e
.getMessage());
150 Session defaultSession
= login(null);
152 String
[] workspaceNames
= defaultSession
.getWorkspace().getAccessibleWorkspaceNames();
153 workspaces
: for (String workspaceName
: workspaceNames
) {
154 if ("security".equals(workspaceName
))
156 perform(workspaceName
);
159 JcrUtils
.logoutQuietly(defaultSession
);
164 public void perform(String workspaceName
) throws RepositoryException
, IOException
{
165 Session session
= login(workspaceName
);
167 String relativePath
= WORKSPACES_BASE
+ workspaceName
+ ".xml";
168 OutputStream xmlOut
= openOutputStream(relativePath
);
169 BackupContentHandler contentHandler
;
170 try (Writer writer
= new BufferedWriter(new OutputStreamWriter(xmlOut
, StandardCharsets
.UTF_8
))) {
171 contentHandler
= new BackupContentHandler(writer
, session
);
173 session
.exportSystemView("/", contentHandler
, true, false);
174 if (log
.isDebugEnabled())
175 log
.debug("Workspace " + workspaceName
+ ": metadata exported to " + relativePath
);
176 } catch (PathNotFoundException e
) {
177 // TODO Auto-generated catch block
179 } catch (SAXException e
) {
180 // TODO Auto-generated catch block
182 } catch (RepositoryException e
) {
183 // TODO Auto-generated catch block
187 for (String path
: contentHandler
.getContentPaths()) {
188 Node contentNode
= session
.getNode(path
);
189 Binary binary
= contentNode
.getProperty(Property
.JCR_DATA
).getBinary();
190 String fileRelativePath
= WORKSPACES_BASE
+ workspaceName
+ contentNode
.getParent().getPath();
191 try (InputStream in
= binary
.getStream(); OutputStream out
= openOutputStream(fileRelativePath
)) {
192 IOUtils
.copy(in
, out
);
193 if (log
.isDebugEnabled())
194 log
.debug("Workspace " + workspaceName
+ ": file content exported to " + fileRelativePath
);
201 // OutputStream xmlOut = openOutputStream(relativePath);
203 // session.exportSystemView("/", xmlOut, false, false);
205 // closeOutputStream(relativePath, xmlOut);
208 // TODO scan all binaries
210 JcrUtils
.logoutQuietly(session
);
214 protected OutputStream
openOutputStream(String relativePath
) throws IOException
{
216 ZipEntry entry
= new ZipEntry(relativePath
);
217 zout
.putNextEntry(entry
);
219 } else if (basePath
!= null) {
220 Path targetPath
= basePath
.resolve(Paths
.get(relativePath
));
221 Files
.createDirectories(targetPath
.getParent());
222 return Files
.newOutputStream(targetPath
);
224 throw new UnsupportedOperationException();
228 protected void closeOutputStream(String relativePath
, OutputStream out
) throws IOException
{
231 } else if (basePath
!= null) {
234 throw new UnsupportedOperationException();
238 protected Session
login(String workspaceName
) {
239 return NodeUtils
.openDataAdminSession(repository
, workspaceName
);