View Javadoc
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 }