]> git.argeo.org Git - lgpl/argeo-commons.git/blob - backup/LogicalBackup.java
Prepare next development cycle
[lgpl/argeo-commons.git] / backup / LogicalBackup.java
1 package org.argeo.maintenance.backup;
2
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;
9 import java.io.Writer;
10 import java.net.URL;
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;
22
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;
30
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.api.NodeUtils;
35 import org.argeo.jcr.JcrUtils;
36 import org.osgi.framework.Bundle;
37 import org.osgi.framework.BundleContext;
38 import org.xml.sax.SAXException;
39
40 /**
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
44 * recovery.
45 */
46 public class LogicalBackup implements Runnable {
47 private final static Log log = LogFactory.getLog(LogicalBackup.class);
48
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;
53
54 private final ZipOutputStream zout;
55 private final Path basePath;
56
57 public LogicalBackup(BundleContext bundleContext, Repository repository, Path basePath) {
58 this.repository = repository;
59 this.zout = null;
60 this.basePath = basePath;
61 this.bundleContext = bundleContext;
62 }
63
64 // public LogicalBackup(BundleContext bundleContext, Repository repository, ZipOutputStream zout) {
65 // this.repository = repository;
66 // this.zout = zout;
67 // this.basePath = null;
68 // this.bundleContext = bundleContext;
69 //}
70
71 @Override
72 public void run() {
73 try {
74 log.info("Start logical backup to " + basePath);
75 perform();
76 } catch (Exception e) {
77 e.printStackTrace();
78 throw new IllegalStateException("Logical backup failed", e);
79 }
80
81 }
82
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);
93 }
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);
106 // }
107 // }
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(""))
113 continue resources;
114 if (entryPath.endsWith("/"))
115 continue resources;
116 String entryName = entryPath.substring(1);// remove first '/'
117 if (entryUrl.getPath().equals("/META-INF/"))
118 continue resources;
119 if (entryUrl.getPath().equals("/META-INF/MANIFEST.MF"))
120 continue resources;
121 // dev
122 if (entryUrl.getPath().startsWith("/target"))
123 continue resources;
124 if (entryUrl.getPath().startsWith("/src"))
125 continue resources;
126 if (entryUrl.getPath().startsWith("/ext"))
127 continue resources;
128
129 if (entryName.startsWith("bin/")) {// dev
130 entryName = entryName.substring("bin/".length());
131 }
132
133 ZipEntry entry = new ZipEntry(entryName);
134 try (InputStream in = entryUrl.openStream()) {
135 try {
136 jarOut.putNextEntry(entry);
137 } catch (ZipException e) {// duplicate
138 continue resources;
139 }
140 IOUtils.copy(in, jarOut);
141 jarOut.closeEntry();
142 // log.info(entryUrl);
143 } catch (FileNotFoundException e) {
144 log.warn(entryUrl + ": " + e.getMessage());
145 }
146 }
147 }
148 }
149
150 Session defaultSession = login(null);
151 try {
152 String[] workspaceNames = defaultSession.getWorkspace().getAccessibleWorkspaceNames();
153 workspaces: for (String workspaceName : workspaceNames) {
154 if ("security".equals(workspaceName))
155 continue workspaces;
156 perform(workspaceName);
157 }
158 } finally {
159 JcrUtils.logoutQuietly(defaultSession);
160 }
161
162 }
163
164 public void perform(String workspaceName) throws RepositoryException, IOException {
165 Session session = login(workspaceName);
166 try {
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);
172 try {
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
178 e.printStackTrace();
179 } catch (SAXException e) {
180 // TODO Auto-generated catch block
181 e.printStackTrace();
182 } catch (RepositoryException e) {
183 // TODO Auto-generated catch block
184 e.printStackTrace();
185 }
186 }
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);
195 } finally {
196
197 }
198
199 }
200
201 // OutputStream xmlOut = openOutputStream(relativePath);
202 // try {
203 // session.exportSystemView("/", xmlOut, false, false);
204 // } finally {
205 // closeOutputStream(relativePath, xmlOut);
206 // }
207
208 // TODO scan all binaries
209 } finally {
210 JcrUtils.logoutQuietly(session);
211 }
212 }
213
214 protected OutputStream openOutputStream(String relativePath) throws IOException {
215 if (zout != null) {
216 ZipEntry entry = new ZipEntry(relativePath);
217 zout.putNextEntry(entry);
218 return zout;
219 } else if (basePath != null) {
220 Path targetPath = basePath.resolve(Paths.get(relativePath));
221 Files.createDirectories(targetPath.getParent());
222 return Files.newOutputStream(targetPath);
223 } else {
224 throw new UnsupportedOperationException();
225 }
226 }
227
228 protected void closeOutputStream(String relativePath, OutputStream out) throws IOException {
229 if (zout != null) {
230 zout.closeEntry();
231 } else if (basePath != null) {
232 out.close();
233 } else {
234 throw new UnsupportedOperationException();
235 }
236 }
237
238 protected Session login(String workspaceName) {
239 return NodeUtils.openDataAdminSession(repository, workspaceName);
240 }
241 }