/* * XmlRestoreHandler.java * * This work is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, * or (at your option) any later version. * * This work is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA * * Copyright (c) 2004-2006 Per Cederberg. All rights reserved. */ package org.liquidsite.app.admin; import java.io.File; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import org.liquidsite.core.content.Content; import org.liquidsite.core.content.ContentException; import org.liquidsite.core.content.ContentManager; import org.liquidsite.core.content.ContentSecurityException; import org.liquidsite.core.content.Domain; import org.liquidsite.core.content.Group; import org.liquidsite.core.content.Permission; import org.liquidsite.core.content.PermissionList; import org.liquidsite.core.content.User; import org.liquidsite.util.log.Log; /** * A SAX XML handler for restoring backups. This class only restores * objects from the XML data, any external files must be restored * separately. * * @author Per Cederberg, <per at percederberg dot net> * @version 1.0 */ class XmlRestoreHandler extends DefaultHandler { /** * The class logger. */ private static final Log LOG = new Log(XmlRestoreHandler.class); /** * The content manager to use. */ private ContentManager manager; /** * The user performing the operation. */ private User managerUser; /** * The content revision restoration mode to use. */ private int mode; /** * The map from old to new content identifiers. */ private HashMap contentIds = new HashMap(); /** * The map from backup file names to new files. */ private HashMap contentFiles = new HashMap(); /** * The domain currently being processed. */ private Domain currentDomain = null; /** * The name of the domain currently being processed. */ private String currentDomainName = null; /** * The user currently being processed. */ private User currentUser = null; /** * The old id of the content object currently being processed. */ private int currentId = 0; /** * The category of the content object currently being processed. */ private int currentCategory = 0; /** * The revisions of the content object currently being processed. */ private ArrayList currentRevisions = null; /** * The content revision currently being processed. */ private Content currentContent = null; /** * The name of the content attribute currently being processed. */ private String currentAttribute = null; /** * The value of the content attribute currently being processed. */ private StringBuffer currentValue = new StringBuffer(); /** * The list permissions currently being processed. */ private ArrayList currentPermissions = null; /** * The restore error log. This log is filled with information * about elements from the backup that were skipped, due to * conflicting data already in the database. An example could be * if an identical hostname was already defined for another * domain. The log will be empty if the data could be completely * restored. */ private StringBuffer errorLog = new StringBuffer(); /** * Creates a new XML backup restore handler. * * @param domainName the new domain to create * @param mode the content revision restoration mode * @param user the user performing the operation * * @throws ContentException if the database couldn't be accessed * properly */ public XmlRestoreHandler(String domainName, int mode, User user) throws ContentException { this.manager = AdminUtils.getContentManager(); this.managerUser = user; this.mode = mode; this.currentDomain = new Domain(manager, domainName); } /** * Returns the restore error log. This log is filled with information * about elements from the backup that were skipped, due to * conflicting data already in the database. An example could be * if an identical hostname was already defined for another * domain. The log will be empty if the data could be completely * restored. * * @return the restore error log, or * an empty string if no errors occurred */ public String getLog() { return errorLog.toString(); } /** * Returns a map from backup file names to new files. The backup * file names should be the locations where the existing files * can be located inside the backup file. The files mapped to * contain the locations where the files should be copied. * * @return a map from backup file names to new files */ public HashMap getContentFiles() { return contentFiles; } /** * Handles the start of an element. * * @param uri the namespace URI * @param localName the local name * @param qName the qualified name * @param attrs the element attributes * * @throws SAXException if the processing of the element failed */ public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException { Group group; User user; Permission perm; String str; try { if (qName.equals("domain")) { currentDomainName = attrs.getValue("name"); currentDomain.setDescription(attrs.getValue("description")); currentDomain.setMailFrom(attrs.getValue("mailfrom")); currentDomain.restore(managerUser); } else if (qName.equals("host")) { try { currentDomain.addHost(attrs.getValue("name"), attrs.getValue("description")); currentDomain.save(managerUser); } catch (ContentException e) { currentDomain.removeHost(attrs.getValue("name")); errorLog.append("skipping restore: "); errorLog.append(e.getMessage()); errorLog.append("\n"); LOG.error("skipping restore", e); } } else if (qName.equals("group")) { str = attrs.getValue("name"); group = new Group(manager, currentDomain, str); group.setDescription(attrs.getValue("description")); if (attrs.getValue("public") != null) { group.setPublic(true); } group.setComment(attrs.getValue("comment")); group.restore(managerUser); } else if (qName.equals("user")) { str = attrs.getValue("name"); currentUser = new User(manager, currentDomain, str); currentUser.setPasswordEncoded(attrs.getValue("password")); if (attrs.getValue("disabled") != null) { currentUser.setEnabled(false); } currentUser.setRealName(attrs.getValue("realname")); currentUser.setEmail(attrs.getValue("email")); currentUser.setComment(attrs.getValue("comment")); } else if (qName.equals("member")) { str = attrs.getValue("group"); group = manager.getGroup(currentDomain, str); currentUser.addToGroup(group); } else if (qName.equals("content")) { if (currentPermissions != null) { createPermissions(); } str = attrs.getValue("id"); currentId = Integer.parseInt(str); str = attrs.getValue("category"); currentCategory = Integer.parseInt(str); currentRevisions = new ArrayList(); } else if (qName.equals("revision")) { currentContent = new Content(manager, currentDomain, currentCategory); str = attrs.getValue("nr"); currentContent.setRevisionNumber(Integer.parseInt(str)); currentContent.setName(attrs.getValue("name")); str = attrs.getValue("parent"); currentContent.setParentId(parseContentId(str)); str = attrs.getValue("online"); currentContent.setOnlineDate(parseDate(str)); str = attrs.getValue("offline"); currentContent.setOfflineDate(parseDate(str)); str = attrs.getValue("modified"); currentContent.setModifiedDate(parseDate(str)); currentContent.setAuthorName(attrs.getValue("author")); currentContent.setComment(attrs.getValue("comment")); } else if (qName.equals("attribute")) { currentAttribute = attrs.getValue("name"); } else if (qName.equals("permission")) { if (currentPermissions == null) { currentPermissions = new ArrayList(); } str = attrs.getValue("user"); if (str != null) { user = manager.getUser(currentDomain, str); } else { user = null; } str = attrs.getValue("group"); if (str != null) { group = manager.getGroup(currentDomain, str); } else { group = null; } perm = new Permission(user, group); str = attrs.getValue("flags"); perm.setRead(str.indexOf("r") >= 0); perm.setWrite(str.indexOf("w") >= 0); perm.setPublish(str.indexOf("p") >= 0); perm.setAdmin(str.indexOf("a") >= 0); currentPermissions.add(perm); } } catch (ContentException e) { LOG.error("restore error", e); throw new SAXException("restore error", e); } catch (ContentSecurityException e) { LOG.error("restore security error", e); throw new SAXException("restore security error", e); } } /** * Handles the end of an element. * * @param uri the namespace URI * @param localName the local name * @param qName the qualified name * * @throws SAXException if the processing of the element failed */ public void endElement(String uri, String localName, String qName) throws SAXException { int id; try { if (qName.equals("domain")) { if (currentPermissions != null) { createPermissions(); } } else if (qName.equals("user")) { currentUser.restore(managerUser); currentUser = null; } else if (qName.equals("content")) { if (mode == 1) { currentContent = findLatestRevision(); if (currentContent.getRevisionNumber() > 0) { currentContent.setRevisionNumber(1); } currentContent.restore(managerUser); addContentFile(); } else if (mode == 2) { currentContent = findLatestRevision(); currentContent.setRevisionNumber(0); currentContent.setOnlineDate(null); currentContent.setOfflineDate(null); currentContent.restore(managerUser); addContentFile(); } else { currentContent = (Content) currentRevisions.get(0); currentContent.restore(managerUser); addContentFile(); id = currentContent.getId(); for (int i = 1; i < currentRevisions.size(); i++) { currentContent = (Content) currentRevisions.get(i); currentContent.setId(id); currentContent.restore(managerUser); addContentFile(); } } contentIds.put(new Integer(currentId), new Integer(currentContent.getId())); if (currentPermissions != null) { createPermissions(); } currentId = 0; currentCategory = 0; currentRevisions = null; currentContent = null; } else if (qName.equals("revision")) { currentRevisions.add(currentContent); currentContent = null; } else if (qName.equals("attribute")) { currentContent.setAttribute(currentAttribute, currentValue.toString()); currentAttribute = null; currentValue.setLength(0); } } catch (ContentException e) { LOG.error("restore error", e); throw new SAXException("restore error", e); } catch (ContentSecurityException e) { LOG.error("restore security error", e); throw new SAXException("restore security error", e); } } /** * Handles characters inside an element. * * @param ch the characters from the XML * @param start the start position in the array * @param length the number of characters to read */ public void characters(char[] ch, int start, int length) { String str; if (currentAttribute != null) { if (currentAttribute.equals("TEMPLATE")) { str = new String(ch, start, length); currentValue.append(parseContentId(str)); } else if (currentAttribute.equals("LINK")) { str = new String(ch, start, length); try { Integer.parseInt(str); str = String.valueOf(parseContentId(str)); } catch (NumberFormatException ignore) { // Do nothing, i.e. store the original text } currentValue.append(str); } else { currentValue.append(ch, start, length); } } } /** * Returns the date representation for a string. * * @param str the date string * * @return the string date representation */ private Date parseDate(String str) { return new Date(Long.parseLong(str)); } /** * Returns the content identifier from the specified string. The * content identifier will be translated from old to new values * if possible, otherwise zero (0) will be returned. * * @param str the numeric string * * @return the new content identifier, or * zero (0) if not found */ private int parseContentId(String str) { Integer value; try { value = new Integer(str); } catch (NumberFormatException e) { return 0; } if (contentIds.containsKey(value)) { return ((Integer) contentIds.get(value)).intValue(); } else { return 0; } } /** * Returns the latest revision from the current ones. * * @return the latest revision */ private Content findLatestRevision() { Content content; Content revision; int pos; content = (Content) currentRevisions.get(0); if (currentRevisions.size() > 1) { pos = currentRevisions.size() - 1; revision = (Content) currentRevisions.get(pos); if (revision.getRevisionNumber() == 0) { content = revision; } } return content; } /** * Adds a content file record if the current content is a file. * * @throws ContentException if the database couldn't be accessed * properly */ private void addContentFile() throws ContentException { String name; String path; File dir; if (currentCategory == Content.FILE_CATEGORY) { name = currentContent.getAttribute("FILENAME"); path = currentDomainName + "/" + currentId + "/" + name; dir = new File(currentDomain.getDirectory(), String.valueOf(currentContent.getId())); contentFiles.put(path, new File(dir, name)); } } /** * Stores the current permissions in the database. * * @throws ContentException if the database couldn't be accessed * properly * @throws ContentSecurityException if the user didn't have the * required permissions */ private void createPermissions() throws ContentException, ContentSecurityException { PermissionList permissions; Permission[] perms; if (currentContent == null) { permissions = new PermissionList(manager, currentDomain); } else { permissions = new PermissionList(manager, currentContent); } perms = new Permission[currentPermissions.size()]; currentPermissions.toArray(perms); permissions.setPermissions(perms); permissions.restore(managerUser); currentPermissions = null; } }