]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.maintenance/src/org/argeo/maintenance/backup/LogicalBackup.java
Improve form framework.
[lgpl/argeo-commons.git] / org.argeo.maintenance / src / org / argeo / maintenance / 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.URI;
11 import java.net.URL;
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;
19 import java.util.Map;
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;
25
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;
33
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;
45
46 /**
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
50 * recovery.
51 */
52 public class LogicalBackup implements Runnable {
53 private final static Log log = LogFactory.getLog(LogicalBackup.class);
54
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;
59
60 private final ZipOutputStream zout;
61 private final Path basePath;
62
63 public LogicalBackup(BundleContext bundleContext, Repository repository, Path basePath) {
64 this.repository = repository;
65 this.zout = null;
66 this.basePath = basePath;
67 this.bundleContext = bundleContext;
68 }
69
70 // public LogicalBackup(BundleContext bundleContext, Repository repository, ZipOutputStream zout) {
71 // this.repository = repository;
72 // this.zout = zout;
73 // this.basePath = null;
74 // this.bundleContext = bundleContext;
75 //}
76
77 @Override
78 public void run() {
79 try {
80 log.info("Start logical backup to " + basePath);
81 perform();
82 } catch (Exception e) {
83 e.printStackTrace();
84 throw new IllegalStateException("Logical backup failed", e);
85 }
86
87 }
88
89 public void perform() throws RepositoryException, IOException {
90 // software backup
91 if (bundleContext != null)
92 performSoftwareBackup();
93
94 // data backup
95 Session defaultSession = login(null);
96 try {
97 String[] workspaceNames = defaultSession.getWorkspace().getAccessibleWorkspaceNames();
98 workspaces: for (String workspaceName : workspaceNames) {
99 if ("security".equals(workspaceName))
100 continue workspaces;
101 perform(workspaceName);
102 }
103 } finally {
104 JcrUtils.logoutQuietly(defaultSession);
105 }
106
107 }
108
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);
119 }
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);
132 // }
133 // }
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(""))
139 continue resources;
140 if (entryPath.endsWith("/"))
141 continue resources;
142 String entryName = entryPath.substring(1);// remove first '/'
143 if (entryUrl.getPath().equals("/META-INF/"))
144 continue resources;
145 if (entryUrl.getPath().equals("/META-INF/MANIFEST.MF"))
146 continue resources;
147 // dev
148 if (entryUrl.getPath().startsWith("/target"))
149 continue resources;
150 if (entryUrl.getPath().startsWith("/src"))
151 continue resources;
152 if (entryUrl.getPath().startsWith("/ext"))
153 continue resources;
154
155 if (entryName.startsWith("bin/")) {// dev
156 entryName = entryName.substring("bin/".length());
157 }
158
159 ZipEntry entry = new ZipEntry(entryName);
160 try (InputStream in = entryUrl.openStream()) {
161 try {
162 jarOut.putNextEntry(entry);
163 } catch (ZipException e) {// duplicate
164 continue resources;
165 }
166 IOUtils.copy(in, jarOut);
167 jarOut.closeEntry();
168 // log.info(entryUrl);
169 } catch (FileNotFoundException e) {
170 log.warn(entryUrl + ": " + e.getMessage());
171 }
172 }
173 }
174 }
175
176 }
177
178 public void perform(String workspaceName) throws RepositoryException, IOException {
179 Session session = login(workspaceName);
180 try {
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);
186 try {
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);
194 }
195 }
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);
204 } finally {
205
206 }
207
208 }
209
210 // OutputStream xmlOut = openOutputStream(relativePath);
211 // try {
212 // session.exportSystemView("/", xmlOut, false, false);
213 // } finally {
214 // closeOutputStream(relativePath, xmlOut);
215 // }
216
217 // TODO scan all binaries
218 } finally {
219 JcrUtils.logoutQuietly(session);
220 }
221 }
222
223 protected OutputStream openOutputStream(String relativePath) throws IOException {
224 if (zout != null) {
225 ZipEntry entry = new ZipEntry(relativePath);
226 zout.putNextEntry(entry);
227 return zout;
228 } else if (basePath != null) {
229 Path targetPath = basePath.resolve(Paths.get(relativePath));
230 Files.createDirectories(targetPath.getParent());
231 return Files.newOutputStream(targetPath);
232 } else {
233 throw new UnsupportedOperationException();
234 }
235 }
236
237 protected void closeOutputStream(String relativePath, OutputStream out) throws IOException {
238 if (zout != null) {
239 zout.closeEntry();
240 } else if (basePath != null) {
241 out.close();
242 } else {
243 throw new UnsupportedOperationException();
244 }
245 }
246
247 protected Session login(String workspaceName) {
248 if (bundleContext != null) {// local
249 return NodeUtils.openDataAdminSession(repository, workspaceName);
250 } else {// remote
251 try {
252 return repository.login(workspaceName);
253 } catch (RepositoryException e) {
254 throw new JcrException(e);
255 }
256 }
257 }
258
259 public final static void main(String[] args) throws Exception {
260 if (args.length == 0) {
261 printUsage("No argument");
262 System.exit(1);
263 }
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);
270 backup.run();
271 }
272
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>]");
277
278 }
279
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);
287 }
288
289 }