package org.exist.backup; import org.exist.Namespaces; import org.exist.client.ClientFrame; import org.exist.dom.DocumentTypeImpl; import org.exist.security.SecurityManager; import org.exist.security.User; import org.exist.storage.DBBroker; import org.exist.util.EXistInputSource; import org.exist.xmldb.CollectionImpl; import org.exist.xmldb.CollectionManagementServiceImpl; import org.exist.xmldb.EXistResource; import org.exist.xmldb.UserManagementService; import org.exist.xmldb.XmldbURI; import org.exist.xquery.XPathException; import org.exist.xquery.util.URIUtils; import org.exist.xquery.value.DateTimeValue; import org.w3c.dom.DocumentType; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; import org.xmldb.api.DatabaseManager; import org.xmldb.api.modules.CollectionManagementService; import org.xmldb.api.base.Collection; import org.xmldb.api.base.Resource; import org.xmldb.api.base.XMLDBException; import javax.swing.*; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Observable; import java.util.Stack; import java.util.Properties; /** * Restore.java * * @author Wolfgang Meier */ public class Restore extends DefaultHandler { private BackupDescriptor contents; private String uri; private String username; private String pass; private XMLReader reader; private CollectionImpl current; private Stack stack = new Stack(); private RestoreDialog dialog = null; private int version=0; private RestoreListener listener; private List<String> errors = new ArrayList<String>(); private static final int strictUriVersion = 1; /** * Constructor for Restore. * @throws XMLDBException * @throws URISyntaxException */ public Restore(String user, String pass, String newAdminPass, File contents, String uri) throws ParserConfigurationException, SAXException, XMLDBException, URISyntaxException { this.username = user; this.pass = pass; this.uri = uri; this.listener = new DefaultListener(); if (newAdminPass != null) setAdminCredentials(newAdminPass); SAXParserFactory saxFactory = SAXParserFactory.newInstance(); saxFactory.setNamespaceAware(true); saxFactory.setValidating(false); SAXParser sax = saxFactory.newSAXParser(); reader = sax.getXMLReader(); reader.setContentHandler(this); do { System.out.println("Contents: " + contents.getAbsolutePath()); BackupDescriptor bd=null; Properties properties = null; try { if(contents.isDirectory()) { bd=new FileSystemBackupDescriptor(new File(new File(contents, "db"), BackupDescriptor.COLLECTION_DESCRIPTOR)); } else if(contents.getName().endsWith(".zip") || contents.getName().endsWith(".ZIP")) { bd=new ZipArchiveBackupDescriptor(contents); } else { bd=new FileSystemBackupDescriptor(contents); } properties = bd.getProperties(); } catch(Exception e) { e.printStackTrace(); throw new SAXException("Unable to create backup descriptor object from "+contents,e); } stack.push(bd); // check if the system collection is in the backup. We have to process // this first to create users. //TODO : find a way to make a corespondance with DBRoker's named constants BackupDescriptor sysbd=bd.getChildBackupDescriptor("system"); if (sysbd!=null) { stack.push(sysbd); } contents = null; if (properties != null && properties.getProperty("incremental", "no").equals("yes")) { String previous = properties.getProperty("previous", ""); if (previous.length() > 0) { contents = new File(bd.getParentDir(), previous); if (!contents.canRead()) throw new SAXException("Required part of incremental backup not found: " + contents.getAbsolutePath()); } } } while (contents != null); } public void setListener(RestoreListener listener) { this.listener = listener; } public void restore(boolean showGUI, JFrame parent) throws XMLDBException, FileNotFoundException, IOException, SAXException { if (showGUI) { dialog = new RestoreDialog(parent, "Restoring data ...", false); dialog.setVisible(true); Thread restoreThread = new Thread() { public void run() { while (!stack.isEmpty()) { try { contents = (BackupDescriptor) stack.pop(); dialog.setBackup(contents.getSymbolicPath()); reader.parse(contents.getInputSource()); } catch (FileNotFoundException e) { dialog.displayMessage(e.getMessage()); } catch (IOException e) { dialog.displayMessage(e.getMessage()); } catch (SAXException e) { dialog.displayMessage(e.getMessage()); } } if (errors.size() > 0) { showErrorMessage("Warnings found during restore", formatErrors()); } dialog.setVisible(false); } }; restoreThread.start(); if(parent == null) { while (restoreThread.isAlive()) { synchronized (this) { try { wait(20); } catch (InterruptedException e) { } } } } } else { while(!stack.isEmpty()) { contents = (BackupDescriptor) stack.pop(); EXistInputSource is = contents.getInputSource(); is.setEncoding("UTF-8"); //restoring sysId reader.parse(is); } if (errors.size() > 0) System.err.println(formatErrors()); } } /** * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes) */ public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { if (namespaceURI.equals(Namespaces.EXIST_NS)) { if (localName.equals("collection")) { final String name = atts.getValue("name"); final String owner = atts.getValue("owner"); final String group = atts.getValue("group"); final String mode = atts.getValue("mode"); final String created = atts.getValue("created"); String strVersion = atts.getValue("version"); if(strVersion!=null) { try { this.version = Integer.parseInt(strVersion); } catch (NumberFormatException e) { this.version=0; } } if (name == null) throw new SAXException("collection requires a name " + "attribute"); try { listener.createCollection(name); XmldbURI collUri; if(version >= strictUriVersion) { collUri = XmldbURI.create(name); } else { try { collUri = URIUtils.encodeXmldbUriFor(name); } catch (URISyntaxException e) { listener.warn("Could not parse document name into a URI: "+e.getMessage()); return; } } Date date_created = null; if (created != null) try { date_created = new DateTimeValue(created).getDate(); } catch (XPathException e2) { } current = mkcol(collUri, date_created); if (current == null) throw new SAXException("Collection not found: " + collUri); UserManagementService service = (UserManagementService) current.getService("UserManagementService", "1.0"); User u = new User(owner, null, group); service.chown(u, group); service.chmod(Integer.parseInt(mode, 8)); } catch (Exception e) { listener.warn("An unrecoverable error occurred while restoring\ncollection '" + name + "'. " + "Aborting restore!"); e.printStackTrace(); throw new SAXException(e.getMessage(), e); } if(dialog != null) dialog.setCollection(name); } else if (localName.equals("subcollection")) { String name = atts.getValue("filename"); if (name == null) { name = atts.getValue("name"); } BackupDescriptor subbd = contents.getChildBackupDescriptor(name); if (subbd!=null) stack.push(subbd); else listener.warn(name + " does not exist or is not readable."); } else if (localName.equals("resource")) { String skip = atts.getValue("skip"); if (skip == null || skip.equals("no")) { String type = atts.getValue("type"); if(type == null) type ="XMLResource"; final String name = atts.getValue("name"); final String owner = atts.getValue("owner"); final String group = atts.getValue("group"); final String perms = atts.getValue("mode"); String filename = atts.getValue("filename"); final String mimetype = atts.getValue("mimetype"); final String created = atts.getValue("created"); final String modified = atts.getValue("modified"); final String publicid = atts.getValue("publicid"); final String systemid = atts.getValue("systemid"); final String namedoctype = atts.getValue("namedoctype"); if (filename == null) filename = name; if (name == null) { listener.warn("Wrong entry in backup descriptor: resource requires a name attribute."); } XmldbURI docUri; if(version >= strictUriVersion) { docUri = XmldbURI.create(name); } else { try { docUri = URIUtils.encodeXmldbUriFor(name); } catch (URISyntaxException e) { listener.warn("Could not parse document name into a URI: "+e.getMessage()); return; } } try { if (dialog != null && current instanceof Observable) { ((Observable) current).addObserver(dialog.getObserver()); } if(dialog != null) dialog.setResource(name); final Resource res = current.createResource(docUri.toString(), type); if (mimetype != null) ((EXistResource)res).setMimeType(mimetype); res.setContent(contents.getContent(filename)); // Restoring name Date date_created = null; Date date_modified = null; if (created != null) try { date_created = (new DateTimeValue(created)).getDate(); } catch (XPathException e2) { listener.warn("Illegal creation date. Skipping ..."); } if (modified != null) try { date_modified = (new DateTimeValue(modified)).getDate(); } catch (XPathException e2) { listener.warn("Illegal modification date. Skipping ..."); } current.storeResource(res, date_created, date_modified); if (publicid != null || systemid != null ) { DocumentType doctype = new DocumentTypeImpl(namedoctype,publicid,systemid ); try { ((EXistResource)res).setDocType(doctype); } catch (XMLDBException e1) { e1.printStackTrace(); } } UserManagementService service = (UserManagementService) current.getService("UserManagementService", "1.0"); User u = new User(owner, null, group); try { service.chown(res, u, group); } catch (XMLDBException e1) { listener.warn("Failed to change owner on document '" + name + "'; skipping ..."); } service.chmod(res, Integer.parseInt(perms, 8)); listener.restored(name); } catch (Exception e) { listener.warn("Failed to restore resource '" + name + "'\nfrom file '" + contents.getSymbolicPath(name,false) + "'.\nReason: " + e.getMessage()); e.printStackTrace(); // throw new RuntimeException(e); } } } else if (localName.equals("deleted")) { final String name = atts.getValue("name"); final String type = atts.getValue("type"); if (type.equals("collection")) { try { Collection child = current.getChildCollection(name); if (child != null) { CollectionManagementService cmgt = (CollectionManagementService) current.getService("CollectionManagementService", "1.0"); cmgt.removeCollection(name); } } catch (XMLDBException e) { listener.warn("Failed to remove deleted collection: " + name + ": " + e.getMessage()); } } else if (type.equals("resource")) { try { Resource resource = current.getResource(name); if (resource != null) current.removeResource(resource); } catch (XMLDBException e) { listener.warn("Failed to remove deleted resource: " + name + ": " + e.getMessage()); } } } } } private final CollectionImpl mkcol(XmldbURI collPath, Date created) throws XMLDBException, URISyntaxException { XmldbURI[] segments = collPath.getPathSegments(); CollectionManagementServiceImpl mgtService; Collection c; XmldbURI dbUri; if (!uri.endsWith(DBBroker.ROOT_COLLECTION)) dbUri = XmldbURI.xmldbUriFor(uri + DBBroker.ROOT_COLLECTION); else dbUri = XmldbURI.xmldbUriFor(uri); Collection current = DatabaseManager.getCollection(dbUri.toString(), username, pass); XmldbURI p = XmldbURI.ROOT_COLLECTION_URI; for(int i=1;i<segments.length;i++) { p = p.append(segments[i]); XmldbURI xmldbURI = dbUri.resolveCollectionPath(p); c = DatabaseManager.getCollection(xmldbURI.toString(), username, pass); if (c == null) { mgtService = (CollectionManagementServiceImpl) current.getService( "CollectionManagementService", "1.0"); //current = mgtService.createCollection(token); current = mgtService.createCollection(segments[i], created); } else current = c; } return (CollectionImpl)current; } private void setAdminCredentials(String adminPassword) throws XMLDBException, URISyntaxException { XmldbURI dbUri; if (!uri.endsWith(DBBroker.ROOT_COLLECTION)) dbUri = XmldbURI.xmldbUriFor(uri + DBBroker.ROOT_COLLECTION); else dbUri = XmldbURI.xmldbUriFor(uri); Collection root = DatabaseManager.getCollection(dbUri.toString(), username, pass); UserManagementService mgmt = (UserManagementService) root.getService("UserManagementService", "1.0"); User dba = mgmt.getUser(SecurityManager.DBA_USER); dba.setPassword(adminPassword); mgmt.updateUser(dba); pass = adminPassword; } private String formatErrors() { StringBuilder builder = new StringBuilder(); builder.append("------------------------------------\n"); for (String error : errors) { builder.append("WARN: ").append(error).append('\n'); } return builder.toString(); } public static void showErrorMessage(String message) { showErrorMessage("Error", message); } public static void showErrorMessage(String title, String message) { JTextArea msgArea = new JTextArea(message); msgArea.setEditable(false); msgArea.setBackground(null); JScrollPane scroll = new JScrollPane(msgArea); JOptionPane optionPane = new JOptionPane(); optionPane.setMessage(new Object[]{scroll}); optionPane.setMessageType(JOptionPane.ERROR_MESSAGE); JDialog dialog = optionPane.createDialog(null, title); dialog.setResizable(true); dialog.pack(); dialog.setVisible(true); return; } public interface RestoreListener { void createCollection(String collection); void restored(String resource); void info(String message); void warn(String message); } private class DefaultListener implements RestoreListener { public void createCollection(String collection) { info("creating collection " + collection); } public void restored(String resource) { info("restored " + resource); } public void info(String message) { if (dialog != null) dialog.displayMessage(message); else System.out.println(message); } public void warn(String message) { if (dialog != null) dialog.displayMessage(message); else System.err.println(message); errors.add(message); } } }