Package org.projectforge.database

Source Code of org.projectforge.database.XmlDump

/////////////////////////////////////////////////////////////////////////////
//
// Project ProjectForge Community Edition
//         www.projectforge.org
//
// Copyright (C) 2001-2014 Kai Reinhard (k.reinhard@micromata.de)
//
// ProjectForge is dual-licensed.
//
// This community edition 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; version 3 of the License.
//
// This community edition 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, see http://www.gnu.org/licenses/.
//
/////////////////////////////////////////////////////////////////////////////

package org.projectforge.database;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import javax.persistence.Transient;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.ClassUtils;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.Validate;
import org.apache.log4j.Logger;
import org.hibernate.EmptyInterceptor;
import org.hibernate.FlushMode;
import org.hibernate.Hibernate;
import org.hibernate.LockOptions;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.projectforge.access.AccessEntryDO;
import org.projectforge.access.GroupTaskAccessDO;
import org.projectforge.common.BeanHelper;
import org.projectforge.common.XStreamHelper;
import org.projectforge.core.AbstractBaseDO;
import org.projectforge.core.ConfigurationDO;
import org.projectforge.database.xstream.HibernateXmlConverter;
import org.projectforge.database.xstream.XStreamSavingConverter;
import org.projectforge.fibu.AbstractRechnungDO;
import org.projectforge.fibu.AbstractRechnungsPositionDO;
import org.projectforge.fibu.AuftragDO;
import org.projectforge.fibu.AuftragsPositionDO;
import org.projectforge.fibu.EingangsrechnungDO;
import org.projectforge.fibu.EingangsrechnungsPositionDO;
import org.projectforge.fibu.EmployeeSalaryDO;
import org.projectforge.fibu.KontoDO;
import org.projectforge.fibu.KundeDO;
import org.projectforge.fibu.ProjektDO;
import org.projectforge.fibu.RechnungDO;
import org.projectforge.fibu.RechnungsPositionDO;
import org.projectforge.fibu.kost.Kost1DO;
import org.projectforge.fibu.kost.Kost2ArtDO;
import org.projectforge.fibu.kost.Kost2DO;
import org.projectforge.fibu.kost.KostZuweisungDO;
import org.projectforge.plugins.core.AbstractPlugin;
import org.projectforge.plugins.core.PluginsRegistry;
import org.projectforge.task.TaskDO;
import org.projectforge.user.GroupDO;
import org.projectforge.user.PFUserDO;
import org.projectforge.user.UserPrefDO;
import org.projectforge.user.UserPrefEntryDO;
import org.projectforge.user.UserRightDO;
import org.springframework.core.io.ClassPathResource;
import org.springframework.orm.hibernate3.HibernateTemplate;

import com.thoughtworks.xstream.XStream;

import de.micromata.hibernate.history.HistoryEntry;

/**
* Dumps and restores the data-base.
* @author Kai Reinhard (k.reinhard@micromata.de)
*
*/
public class XmlDump
{
  private static final Logger log = Logger.getLogger(XmlDump.class);

  private static final String XML_DUMP_FILENAME = System.getProperty("user.home") + "/tmp/database-dump.xml.gz";

  private HibernateTemplate hibernate;

  private final List<XmlDumpHook> xmlDumpHooks = new LinkedList<XmlDumpHook>();

  /**
   * These classes are stored automatically because they're dependent.
   */
  private final Class< ? >[] embeddedClasses = new Class< ? >[] { UserRightDO.class, AuftragsPositionDO.class,
      EingangsrechnungsPositionDO.class, RechnungsPositionDO.class};

  public HibernateTemplate getHibernate()
  {
    Validate.notNull(hibernate);
    return hibernate;
  }

  public void setHibernate(final HibernateTemplate hibernate)
  {
    this.hibernate = hibernate;
  }

  public void registerHook(final XmlDumpHook xmlDumpHook)
  {
    for (final XmlDumpHook hook : xmlDumpHooks) {
      if (hook.getClass().equals(xmlDumpHook.getClass()) == true) {
        log.error("Can't register XmlDumpHook twice: " + xmlDumpHook);
        return;
      }
    }
    xmlDumpHooks.add(xmlDumpHook);
  }

  /**
   * @return Only for test cases.
   */
  public XStreamSavingConverter restoreDatabase()
  {
    try {
      return restoreDatabase(new InputStreamReader(new FileInputStream(XML_DUMP_FILENAME), "utf-8"));
    } catch (final UnsupportedEncodingException ex) {
      log.error(ex.getMessage(), ex);
      throw new RuntimeException(ex);
    } catch (final FileNotFoundException ex) {
      log.error(ex.getMessage(), ex);
      throw new RuntimeException(ex);
    }
  }

  /**
   * @param reader
   * @return Only for test cases.
   */
  public XStreamSavingConverter restoreDatabase(final Reader reader)
  {
    final List<AbstractPlugin> plugins = PluginsRegistry.instance().getPlugins();
    final XStreamSavingConverter xstreamSavingConverter = new XStreamSavingConverter() {

      @Override
      protected Serializable getOriginalIdentifierValue(final Object obj)
      {
        return HibernateUtils.getIdentifier(obj);
      }

      @Override
      public Serializable onBeforeSave(final Session session, final Object obj)
      {
        log.info("Object " + obj);
        if (obj instanceof PFUserDO) {
          final PFUserDO user = (PFUserDO) obj;
          return save(user, user.getRights());
        } else if (obj instanceof AbstractRechnungDO< ? >) {
          final AbstractRechnungDO< ? extends AbstractRechnungsPositionDO> rechnung = (AbstractRechnungDO< ? >) obj;
          final List< ? extends AbstractRechnungsPositionDO> positions = rechnung.getPositionen();
          final KontoDO konto = rechnung.getKonto();
          if (konto != null) {
            save(konto);
            rechnung.setKonto(null);
          }
          rechnung.setPositionen(null); // Need to nullable positions first (otherwise insert fails).
          final Serializable id = save(rechnung);
          if (konto != null) {
            rechnung.setKonto(konto);
          }
          if (positions != null) {
            for (final AbstractRechnungsPositionDO pos : positions) {
              if (pos.getKostZuweisungen() != null) {
                final List<KostZuweisungDO> zuweisungen = pos.getKostZuweisungen();
                pos.setKostZuweisungen(null); // Need to nullable first (otherwise insert fails).
                save(pos);
                if (pos instanceof RechnungsPositionDO) {
                  ((RechnungDO) rechnung).addPosition((RechnungsPositionDO) pos);
                } else {
                  ((EingangsrechnungDO) rechnung).addPosition((EingangsrechnungsPositionDO) pos);
                }
                if (zuweisungen != null) {
                  for (final KostZuweisungDO zuweisung : zuweisungen) {
                    pos.addKostZuweisung(zuweisung);
                    save(zuweisung);
                  }
                }
              }
            }
          }
          return id;
        } else if (obj instanceof AuftragDO) {
          final AuftragDO auftrag = (AuftragDO) obj;
          return save(auftrag, auftrag.getPositionen());
        }
        if (plugins != null) {
          for (final AbstractPlugin plugin : plugins) {
            try {
              plugin.onBeforeRestore(this, obj);
            } catch (final Exception ex) {
              log.error("Error in Plugin while restoring object: " + ex.getMessage(), ex);
            }
          }
        }
        for (final XmlDumpHook xmlDumpHook : xmlDumpHooks) {
          try {
            xmlDumpHook.onBeforeRestore(this, obj);
          } catch (final Exception ex) {
            log.error("Error in XmlDumpHook while restoring object: " + ex.getMessage(), ex);
          }
        }
        return super.onBeforeSave(session, obj);
      }

      /**
       * @see org.projectforge.database.xstream.XStreamSavingConverter#onAfterSave(java.lang.Object, java.io.Serializable)
       */
      @Override
      public void onAfterSave(final Object obj, final Serializable id)
      {
        if (plugins != null) {
          for (final AbstractPlugin plugin : plugins) {
            plugin.onAfterRestore(this, obj, id);
          }
        }
      }
    };
    // UserRightDO is inserted on cascade while inserting PFUserDO.
    xstreamSavingConverter.appendIgnoredObjects(embeddedClasses);
    xstreamSavingConverter.appendOrderedType(PFUserDO.class, GroupDO.class, TaskDO.class, KundeDO.class, ProjektDO.class, Kost1DO.class,
        Kost2ArtDO.class, Kost2DO.class, AuftragDO.class, //
        RechnungDO.class, EingangsrechnungDO.class, EmployeeSalaryDO.class, KostZuweisungDO.class,//
        UserPrefEntryDO.class, UserPrefDO.class, //
        AccessEntryDO.class, GroupTaskAccessDO.class, ConfigurationDO.class);
    if (plugins != null) {
      for (final AbstractPlugin plugin : plugins) {
        xstreamSavingConverter.appendOrderedType(plugin.getPersistentEntities());
      }
    }
    Session session = null;
    try {
      final SessionFactory sessionFactory = hibernate.getSessionFactory();
      session = sessionFactory.openSession(EmptyInterceptor.INSTANCE);
      session.setFlushMode(FlushMode.AUTO);
      final XStream xstream = XStreamHelper.createXStream();
      xstream.setMode(XStream.ID_REFERENCES);
      xstreamSavingConverter.setSession(session);
      xstream.registerConverter(xstreamSavingConverter, 10);
      xstream.registerConverter(new UserRightIdSingleValueConverter(), 20);
      xstream.registerConverter(new UserPrefAreaSingleValueConverter(), 19);
      // alle Objekte Laden und speichern
      xstream.fromXML(reader);

      xstreamSavingConverter.saveObjects();
    } catch (final Exception ex) {
      log.error(ex.getMessage(), ex);
      throw new RuntimeException(ex);
    } finally {
      IOUtils.closeQuietly(reader);
      if (session != null) {
        session.close();
      }
    }
    return xstreamSavingConverter;
  }

  /**
   * @return Only for test cases.
   */
  public XStreamSavingConverter restoreDatabaseFromClasspathResource(final String path, final String encoding)
  {
    final ClassPathResource cpres = new ClassPathResource(path);
    Reader reader;
    try {
      InputStream in;
      if (path.endsWith(".gz") == true) {
        in = new GZIPInputStream(cpres.getInputStream());
      } else {
        in = cpres.getInputStream();
      }
      reader = new InputStreamReader(in, encoding);
    } catch (final IOException ex) {
      log.error(ex.getMessage(), ex);
      throw new RuntimeException(ex);
    }
    return restoreDatabase(reader);
  }

  public void dumpDatabase()
  {
    dumpDatabase(XML_DUMP_FILENAME, "utf-8");
  }

  /**
   *
   * @param filename virtual filename: If the filename suffix is "gz" then the dump will be compressed.
   * @param out
   */
  public void dumpDatabase(final String filename, final OutputStream out)
  {
    final HibernateXmlConverter converter = new HibernateXmlConverter() {
      @Override
      protected void init(final XStream xstream)
      {
        xstream.omitField(AbstractBaseDO.class, "minorChange");
        xstream.omitField(AbstractBaseDO.class, "selected");
        xstream.registerConverter(new UserRightIdSingleValueConverter(), 20);
        xstream.registerConverter(new UserPrefAreaSingleValueConverter(), 19);
      }
    };
    converter.setHibernate(hibernate);
    converter.appendIgnoredTopLevelObjects(embeddedClasses);
    Writer writer = null;
    GZIPOutputStream gzipOut = null;
    try {
      if (filename.endsWith(".gz") == true) {
        gzipOut = new GZIPOutputStream(out);
        writer = new OutputStreamWriter(gzipOut, "utf-8");
      } else {
        writer = new OutputStreamWriter(out, "utf-8");
      }
      converter.dumpDatabaseToXml(writer, true); // history=false, preserveIds=true
    } catch (final IOException ex) {
      log.error(ex.getMessage(), ex);
    } finally {
      IOUtils.closeQuietly(gzipOut);
      IOUtils.closeQuietly(writer);
    }
  }

  public void dumpDatabase(final String path, final String encoding)
  {
    OutputStream out = null;
    try {
      out = new FileOutputStream(path);
      dumpDatabase(path, out);
    } catch (final IOException ex) {
      log.error(ex.getMessage(), ex);
    } finally {
      IOUtils.closeQuietly(out);
    }
  }

  /**
   * Verify the imported dump.
   * @return Number of checked objects. This number is negative if any error occurs (at least one object wasn't imported successfully).
   */
  public int verifyDump(final XStreamSavingConverter xstreamSavingConverter)
  {
    final SessionFactory sessionFactory = hibernate.getSessionFactory();
    Session session = null;
    boolean hasError = false;
    try {
      session = sessionFactory.openSession(EmptyInterceptor.INSTANCE);
      session.setDefaultReadOnly(true);
      int counter = 0;
      for (final Map.Entry<Class< ? >, List<Object>> entry : xstreamSavingConverter.getAllObjects().entrySet()) {
        final List<Object> objects = entry.getValue();
        final Class< ? > entityClass = entry.getKey();
        if (objects == null) {
          continue;
        }
        for (final Object obj : objects) {
          if (HibernateUtils.isEntity(obj.getClass()) == false) {
            continue;
          }
          final Serializable id = HibernateUtils.getIdentifier(obj);
          if (id == null) {
            // Can't compare this object without identifier.
            continue;
          }
          // log.info("Testing object: " + obj);
          final Object databaseObject = session.get(entityClass, id, LockOptions.READ);
          Hibernate.initialize(databaseObject);
          final boolean equals = equals(obj, databaseObject, true);
          if (equals == false) {
            log.error("Object not sucessfully imported! xml object=[" + obj + "], data base=[" + databaseObject + "]");
            hasError = true;
          }
          ++counter;
        }
      }
      for (final HistoryEntry historyEntry : xstreamSavingConverter.getHistoryEntries()) {
        final Class< ? > type = xstreamSavingConverter.getClassFromHistoryName(historyEntry.getClassName());
        final Object o = type != null ? session.get(type, historyEntry.getEntityId()) : null;
        if (o == null) {
          log.warn("A corrupted history entry found (entity of class '"
              + historyEntry.getClassName()
              + "' with id "
              + historyEntry.getEntityId()
              + " not found: "
              + historyEntry
              + ". This doesn't affect the functioning of ProjectForge, this may result in orphaned history entries.");
          hasError = true;
        }
        ++counter;
      }
      if (hasError == true) {
        log.fatal("*********** A inconsistency in the import was found! This may result in a data loss or corrupted data! Please retry the import. "
            + counter
            + " entries checked.");
        return -counter;
      }
      log.info("Data-base import successfully verified: " + counter + " entries checked.");
      return counter;
    } finally {
      if (session != null) {
        session.close();
      }
    }
  }

  /**
   * @param o1
   * @param o2
   * @param logDifference If true than the difference is logged.
   * @return True if the given objects are equal.
   */
  private boolean equals(final Object o1, final Object o2, final boolean logDifference)
  {
    if (o1 == null) {
      final boolean equals = (o2 == null);
      if (equals == false && logDifference == true) {
        log.error("Value 1 is null and value 2 is " + o2);
      }
      return equals;
    } else if (o2 == null) {
      if (logDifference == true) {
        log.error("Value 2 is null and value 1 is " + o1);
      }
      return false;
    }
    final Class< ? > cls1 = o1.getClass();
    final Field[] fields = cls1.getDeclaredFields();
    AccessibleObject.setAccessible(fields, true);
    for (final Field field : fields) {
      if (accept(field) == false) {
        continue;
      }
      try {
        final Object fieldValue1 = getValue(o1, o2, field);
        final Object fieldValue2 = getValue(o2, o1, field);
        if (field.getType().isPrimitive() == true) {
          if (ObjectUtils.equals(fieldValue2, fieldValue1) == false) {
            if (logDifference == true) {
              log.error("Field '" + field.getName() + "': value 1 '" + fieldValue1 + "' is different from value 2 '" + fieldValue2 + "'.");
            }
            return false;
          }
          continue;
        } else if (fieldValue1 == null) {
          if (fieldValue2 != null) {
            if (fieldValue2 instanceof Collection< ? >) {
              if (CollectionUtils.isEmpty((Collection< ? >) fieldValue2) == true) {
                // null is equals to empty collection in this case.
                return true;
              }
            }
            if (logDifference == true) {
              log.error("Field '" + field.getName() + "': value 1 '" + fieldValue1 + "' is different from value 2 '" + fieldValue2 + "'.");
            }
            return false;
          }
        } else if (fieldValue2 == null) {
          if (fieldValue1 != null) {
            if (logDifference == true) {
              log.error("Field '" + field.getName() + "': value 1 '" + fieldValue1 + "' is different from value 2 '" + fieldValue2 + "'.");
            }
            return false;
          }
        } else if (fieldValue1 instanceof Collection< ? >) {
          final Collection< ? > col1 = (Collection< ? >) fieldValue1;
          final Collection< ? > col2 = (Collection< ? >) fieldValue2;
          if (col1.size() != col2.size()) {
            if (logDifference == true) {
              log.error("Field '"
                  + field.getName()
                  + "': colection's size '"
                  + col1.size()
                  + "' is different from collection's size '"
                  + col2.size()
                  + "'.");
            }
            return false;
          }
          if (equals(field, col1, col2, logDifference) == false || equals(field, col2, col1, logDifference) == false) {
            return false;
          }
        } else if (HibernateUtils.isEntity(fieldValue1.getClass()) == true) {
          if (fieldValue2 == null
              || ObjectUtils.equals(HibernateUtils.getIdentifier(fieldValue1), HibernateUtils.getIdentifier(fieldValue2)) == false) {
            if (logDifference == true) {
              log.error("Field '"
                  + field.getName()
                  + "': Hibernate object id '"
                  + HibernateUtils.getIdentifier(fieldValue1)
                  + "' is different from id '"
                  + HibernateUtils.getIdentifier(fieldValue2)
                  + "'.");
            }
            return false;
          }
        } else if (fieldValue1 instanceof BigDecimal) {
          if (fieldValue2 == null || ((BigDecimal) fieldValue1).compareTo((BigDecimal) fieldValue2) != 0) {
            if (logDifference == true) {
              log.error("Field '" + field.getName() + "': value 1 '" + fieldValue1 + "' is different from value 2 '" + fieldValue2 + "'.");
            }
            return false;
          }
        } else if (fieldValue1.getClass().isArray() == true) {
          if (ArrayUtils.isEquals(fieldValue1, fieldValue2) == false) {
            if (logDifference == true) {
              log.error("Field '" + field.getName() + "': value 1 '" + fieldValue1 + "' is different from value 2 '" + fieldValue2 + "'.");
            }
            return false;
          }
        } else if (ObjectUtils.equals(fieldValue2, fieldValue1) == false) {
          if (logDifference == true) {
            log.error("Field '" + field.getName() + "': value 1 '" + fieldValue1 + "' is different from value 2 '" + fieldValue2 + "'.");
          }
          return false;
        }
      } catch (final IllegalAccessException ex) {
        throw new InternalError("Unexpected IllegalAccessException: " + ex.getMessage());
      }
    }
    return true;
  }

  /**
   * Tests if every entry of col1 is found as equals entry in col2. You need to call this method twice with swapped params for being sure of
   * equality!
   * @param col1
   * @param col2
   * @return
   */
  private boolean equals(final Field field, final Collection< ? > col1, final Collection< ? > col2, final boolean logDifference)
  {
    for (final Object colVal1 : col1) {
      boolean equals = false;
      for (final Object colVal2 : col2) {
        if (equals(colVal1, colVal2, false) == true) {
          equals = true; // Equal object found.
          break;
        }
      }
      if (equals == false) {
        if (logDifference == true) {
          log.error("Field '" + field.getName() + "': value '" + colVal1 + "' not found in other collection.");
        }
        return false;
      }
    }
    return true;
  }

  /**
   * @param obj
   * @param compareObj Only need for @Transient (because Javassist proxy doesn't have this annotion).
   * @param field
   * @return
   * @throws IllegalArgumentException
   * @throws IllegalAccessException
   */
  private Object getValue(final Object obj, final Object compareObj, final Field field) throws IllegalArgumentException,
  IllegalAccessException
  {
    Object val = null;
    final Method getter = BeanHelper.determineGetter(obj.getClass(), field.getName());
    final Method getter2 = BeanHelper.determineGetter(compareObj.getClass(), field.getName());
    if (getter != null
        && getter.isAnnotationPresent(Transient.class) == false
        && getter2 != null
        && getter2.isAnnotationPresent(Transient.class) == false) {
      val = BeanHelper.invoke(obj, getter);
    }
    if (val == null) {
      val = field.get(obj);
    }
    return val;
  }

  /**
   * @param field
   * @return true, if the given field should be compared.
   */
  protected boolean accept(final Field field)
  {
    if (field.getName().indexOf(ClassUtils.INNER_CLASS_SEPARATOR_CHAR) != -1) {
      // Reject field from inner class.
      return false;
    }
    if (field.getName().equals("handler") == true) {
      // Handler of Javassist proxy should be ignored.
      return false;
    }
    if (Modifier.isTransient(field.getModifiers()) == true) {
      // transients.
      return false;
    }
    if (Modifier.isStatic(field.getModifiers()) == true) {
      // transients.
      return false;
    }
    return true;
  }
}
TOP

Related Classes of org.projectforge.database.XmlDump

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.