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
;
12 import java
.nio
.charset
.StandardCharsets
;
13 import java
.nio
.file
.Files
;
14 import java
.nio
.file
.Path
;
15 import java
.nio
.file
.Paths
;
16 import java
.util
.Dictionary
;
17 import java
.util
.Enumeration
;
18 import java
.util
.HashMap
;
20 import java
.util
.jar
.JarOutputStream
;
21 import java
.util
.jar
.Manifest
;
22 import java
.util
.zip
.ZipEntry
;
23 import java
.util
.zip
.ZipException
;
24 import java
.util
.zip
.ZipOutputStream
;
26 import javax
.jcr
.Binary
;
27 import javax
.jcr
.Node
;
28 import javax
.jcr
.Property
;
29 import javax
.jcr
.Repository
;
30 import javax
.jcr
.RepositoryException
;
31 import javax
.jcr
.RepositoryFactory
;
32 import javax
.jcr
.Session
;
34 import org
.apache
.commons
.io
.IOUtils
;
35 import org
.apache
.commons
.logging
.Log
;
36 import org
.apache
.commons
.logging
.LogFactory
;
37 import org
.argeo
.api
.NodeConstants
;
38 import org
.argeo
.api
.NodeUtils
;
39 import org
.argeo
.jackrabbit
.client
.ClientDavexRepositoryFactory
;
40 import org
.argeo
.jcr
.JcrException
;
41 import org
.argeo
.jcr
.JcrUtils
;
42 import org
.osgi
.framework
.Bundle
;
43 import org
.osgi
.framework
.BundleContext
;
44 import org
.xml
.sax
.SAXException
;
47 * Performs a backup of the data based only on programmatic interfaces. Useful
48 * for migration or live backup. Physical backups of the underlying file
49 * systems, databases, LDAP servers, etc. should be performed for disaster
52 public class LogicalBackup
implements Runnable
{
53 private final static Log log
= LogFactory
.getLog(LogicalBackup
.class);
55 public final static String WORKSPACES_BASE
= "workspaces/";
56 public final static String OSGI_BASE
= "share/osgi/";
57 private final Repository repository
;
58 private final BundleContext bundleContext
;
60 private final ZipOutputStream zout
;
61 private final Path basePath
;
63 public LogicalBackup(BundleContext bundleContext
, Repository repository
, Path basePath
) {
64 this.repository
= repository
;
66 this.basePath
= basePath
;
67 this.bundleContext
= bundleContext
;
70 // public LogicalBackup(BundleContext bundleContext, Repository repository, ZipOutputStream zout) {
71 // this.repository = repository;
73 // this.basePath = null;
74 // this.bundleContext = bundleContext;
80 log
.info("Start logical backup to " + basePath
);
82 } catch (Exception e
) {
84 throw new IllegalStateException("Logical backup failed", e
);
89 public void perform() throws RepositoryException
, IOException
{
91 if (bundleContext
!= null)
92 performSoftwareBackup();
95 Session defaultSession
= login(null);
97 String
[] workspaceNames
= defaultSession
.getWorkspace().getAccessibleWorkspaceNames();
98 workspaces
: for (String workspaceName
: workspaceNames
) {
99 if ("security".equals(workspaceName
))
101 perform(workspaceName
);
104 JcrUtils
.logoutQuietly(defaultSession
);
109 public void performSoftwareBackup() throws IOException
{
110 for (Bundle bundle
: bundleContext
.getBundles()) {
111 String relativePath
= OSGI_BASE
+ "boot/" + bundle
.getSymbolicName() + ".jar";
112 Dictionary
<String
, String
> headers
= bundle
.getHeaders();
113 Manifest manifest
= new Manifest();
114 Enumeration
<String
> headerKeys
= headers
.keys();
115 while (headerKeys
.hasMoreElements()) {
116 String headerKey
= headerKeys
.nextElement();
117 String headerValue
= headers
.get(headerKey
);
118 manifest
.getMainAttributes().putValue(headerKey
, headerValue
);
120 try (JarOutputStream jarOut
= new JarOutputStream(openOutputStream(relativePath
), manifest
)) {
121 // Enumeration<String> entryPaths = bundle.getEntryPaths("/");
122 // while (entryPaths.hasMoreElements()) {
123 // String entryPath = entryPaths.nextElement();
124 // ZipEntry entry = new ZipEntry(entryPath);
125 // URL entryUrl = bundle.getEntry(entryPath);
126 // try (InputStream in = entryUrl.openStream()) {
127 // jarOut.putNextEntry(entry);
128 // IOUtils.copy(in, jarOut);
129 // jarOut.closeEntry();
130 // } catch (FileNotFoundException e) {
131 // log.warn(entryPath);
134 Enumeration
<URL
> resourcePaths
= bundle
.findEntries("/", "*", true);
135 resources
: while (resourcePaths
.hasMoreElements()) {
136 URL entryUrl
= resourcePaths
.nextElement();
137 String entryPath
= entryUrl
.getPath();
138 if (entryPath
.equals(""))
140 if (entryPath
.endsWith("/"))
142 String entryName
= entryPath
.substring(1);// remove first '/'
143 if (entryUrl
.getPath().equals("/META-INF/"))
145 if (entryUrl
.getPath().equals("/META-INF/MANIFEST.MF"))
148 if (entryUrl
.getPath().startsWith("/target"))
150 if (entryUrl
.getPath().startsWith("/src"))
152 if (entryUrl
.getPath().startsWith("/ext"))
155 if (entryName
.startsWith("bin/")) {// dev
156 entryName
= entryName
.substring("bin/".length());
159 ZipEntry entry
= new ZipEntry(entryName
);
160 try (InputStream in
= entryUrl
.openStream()) {
162 jarOut
.putNextEntry(entry
);
163 } catch (ZipException e
) {// duplicate
166 IOUtils
.copy(in
, jarOut
);
168 // log.info(entryUrl);
169 } catch (FileNotFoundException e
) {
170 log
.warn(entryUrl
+ ": " + e
.getMessage());
178 public void perform(String workspaceName
) throws RepositoryException
, IOException
{
179 Session session
= login(workspaceName
);
181 String relativePath
= WORKSPACES_BASE
+ workspaceName
+ ".xml";
182 OutputStream xmlOut
= openOutputStream(relativePath
);
183 BackupContentHandler contentHandler
;
184 try (Writer writer
= new BufferedWriter(new OutputStreamWriter(xmlOut
, StandardCharsets
.UTF_8
))) {
185 contentHandler
= new BackupContentHandler(writer
, session
);
187 session
.exportSystemView("/", contentHandler
, true, false);
188 if (log
.isDebugEnabled())
189 log
.debug("Workspace " + workspaceName
+ ": metadata exported to " + relativePath
);
190 } catch (SAXException e
) {
191 throw new RuntimeException("Cannot perform backup of workspace " + workspaceName
, e
);
192 } catch (RepositoryException e
) {
193 throw new JcrException("Cannot perform backup of workspace " + workspaceName
, e
);
196 for (String path
: contentHandler
.getContentPaths()) {
197 Node contentNode
= session
.getNode(path
);
198 Binary binary
= contentNode
.getProperty(Property
.JCR_DATA
).getBinary();
199 String fileRelativePath
= WORKSPACES_BASE
+ workspaceName
+ contentNode
.getParent().getPath();
200 try (InputStream in
= binary
.getStream(); OutputStream out
= openOutputStream(fileRelativePath
)) {
201 IOUtils
.copy(in
, out
);
202 if (log
.isTraceEnabled())
203 log
.trace("Workspace " + workspaceName
+ ": file content exported to " + fileRelativePath
);
210 // OutputStream xmlOut = openOutputStream(relativePath);
212 // session.exportSystemView("/", xmlOut, false, false);
214 // closeOutputStream(relativePath, xmlOut);
217 // TODO scan all binaries
219 JcrUtils
.logoutQuietly(session
);
223 protected OutputStream
openOutputStream(String relativePath
) throws IOException
{
225 ZipEntry entry
= new ZipEntry(relativePath
);
226 zout
.putNextEntry(entry
);
228 } else if (basePath
!= null) {
229 Path targetPath
= basePath
.resolve(Paths
.get(relativePath
));
230 Files
.createDirectories(targetPath
.getParent());
231 return Files
.newOutputStream(targetPath
);
233 throw new UnsupportedOperationException();
237 protected void closeOutputStream(String relativePath
, OutputStream out
) throws IOException
{
240 } else if (basePath
!= null) {
243 throw new UnsupportedOperationException();
247 protected Session
login(String workspaceName
) {
248 if (bundleContext
!= null) {// local
249 return NodeUtils
.openDataAdminSession(repository
, workspaceName
);
252 return repository
.login(workspaceName
);
253 } catch (RepositoryException e
) {
254 throw new JcrException(e
);
259 public final static void main(String
[] args
) throws Exception
{
260 if (args
.length
== 0) {
261 printUsage("No argument");
264 URI uri
= new URI(args
[0]);
265 Repository repository
= createRemoteRepository(uri
);
266 Path basePath
= args
.length
> 1 ? Paths
.get(args
[1]) : Paths
.get(System
.getProperty("user.dir"));
267 if (!Files
.exists(basePath
))
268 Files
.createDirectories(basePath
);
269 LogicalBackup backup
= new LogicalBackup(null, repository
, basePath
);
273 private static void printUsage(String errorMessage
) {
274 if (errorMessage
!= null)
275 System
.err
.println(errorMessage
);
276 System
.out
.println("Usage: LogicalBackup <remote URL> [<target directory>]");
280 protected static Repository
createRemoteRepository(URI uri
) throws RepositoryException
{
281 RepositoryFactory repositoryFactory
= new ClientDavexRepositoryFactory();
282 Map
<String
, String
> params
= new HashMap
<String
, String
>();
283 params
.put(ClientDavexRepositoryFactory
.JACKRABBIT_DAVEX_URI
, uri
.toString());
284 // TODO make it configurable
285 params
.put(ClientDavexRepositoryFactory
.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE
, NodeConstants
.SYS_WORKSPACE
);
286 return repositoryFactory
.getRepository(params
);