/////////////////////////////////////////////////////////////////////////////
//
// 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.xstream;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.PredicateUtils;
import org.hibernate.EntityMode;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.proxy.HibernateProxyHelper;
import org.projectforge.database.HibernateEntities;
import org.springframework.dao.DataAccessException;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.orm.hibernate3.HibernateTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import com.thoughtworks.xstream.MarshallingStrategy;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider;
import com.thoughtworks.xstream.io.xml.CompactWriter;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.mapper.MapperWrapper;
import de.micromata.hibernate.history.delta.AssociationPropertyDelta;
import de.micromata.hibernate.history.delta.CollectionPropertyDelta;
import de.micromata.hibernate.history.delta.PropertyDelta;
import de.micromata.hibernate.history.delta.SimplePropertyDelta;
import de.micromata.hibernate.spring.NullWriter;
import de.micromata.hibernate.spring.ProxyIdRefMarshallingStrategy;
/**
* Hilfsklasse zum Laden und Speichern einer gesamten Hibernate-Datenbank im XML-Format. Zur Darstellung der Daten in XML wird XStream zur
* Serialisierung eingesetzt. Alle Lazy-Objekte aus Hibernate werden vollständig initialisiert. http://jira.codehaus.org/browse/XSTR-377
*
* @author Wolfgang Jung (w.jung@micromata.de)
*
*/
public class HibernateXmlConverter
{
/** The logger */
private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(HibernateXmlConverter.class);
/** the wrapper to hibernate */
private HibernateTemplate hibernate;
// Ignore these objects listing in the top level list saving because the are saved implicit by their parent objects.
private final Set<Class< ? >> ignoreFromTopLevelListing = new HashSet<Class< ? >>();
public HibernateXmlConverter()
{
this.ignoreFromTopLevelListing.add(PropertyDelta.class);
this.ignoreFromTopLevelListing.add(SimplePropertyDelta.class);
this.ignoreFromTopLevelListing.add(AssociationPropertyDelta.class);
this.ignoreFromTopLevelListing.add(CollectionPropertyDelta.class);
}
/**
* Initialisierung der Hibernate-verbindung.
*
* @param hibernate ein bereits initialisiertes HibernateTemplate
*/
public void setHibernate(final HibernateTemplate hibernate)
{
this.hibernate = hibernate;
}
/**
* Schreibt alle Objekte der Datenbank in den angegebenen Writer.<br/>
* <b>Warnung!</b> Bei der Serialisierung von Collections wird derzeit nur {@link java.util.Set} sauber unterstützt.
* @param writer Ziel für die XML-Datei.
* @param includeHistory bei false werden die History Einträge nicht geschrieben
*/
public void dumpDatabaseToXml(final Writer writer, final boolean includeHistory)
{
dumpDatabaseToXml(writer, includeHistory, true);
}
/**
* Schreibt alle Objekte der Datenbank in den angegebenen Writer.<br/>
* <b>Warnung!</b> Bei der Serialisierung von Collections wird derzeit nur {@link java.util.Set} sauber unterstützt.
* @param writer Ziel für die XML-Datei.
* @param includeHistory bei false werden die History Einträge nicht geschrieben
* @param preserveIds If true, the object ids will be preserved, otherwise new ids will be assigned through xstream.
*/
public void dumpDatabaseToXml(final Writer writer, final boolean includeHistory, final boolean preserveIds)
{
final TransactionTemplate tx = new TransactionTemplate(new HibernateTransactionManager(hibernate.getSessionFactory()));
tx.execute(new TransactionCallback() {
public Object doInTransaction(final TransactionStatus status)
{
hibernate.execute(new HibernateCallback() {
public Object doInHibernate(final Session session) throws HibernateException
{
writeObjects(writer, includeHistory, session, preserveIds);
status.setRollbackOnly();
return null;
}
});
return null;
}
});
}
public HibernateXmlConverter appendIgnoredTopLevelObjects(final Class< ? >... types)
{
if (types != null) {
for (final Class< ? > type : types) {
this.ignoreFromTopLevelListing.add(type);
}
}
return this;
}
/**
* @param writer
* @param includeHistory
* @param session
* @throws DataAccessException
* @throws HibernateException
*/
private void writeObjects(final Writer writer, final boolean includeHistory, final Session session, final boolean preserveIds)
throws DataAccessException, HibernateException
{
// Container für die Objekte
final List<Object> all = new ArrayList<Object>();
final XStream stream = initXStream(session, true);
final XStream defaultXStream = initXStream(session, false);
session.flush();
// Alles laden
final List<Class< ? >> entities = new ArrayList<Class< ? >>();
entities.addAll(HibernateEntities.instance().getOrderedEntities());
entities.addAll(HibernateEntities.instance().getOrderedHistoryEntities());
for (final Class< ? > entityClass : entities) {
final String entitySimpleName = entityClass.getSimpleName();
final String entityType = entityClass.getName();
if (includeHistory == false && entityType.startsWith("de.micromata.hibernate.history.") == true) {
// Skip history entries.
continue;
}
List< ? > list = session.createQuery("select o from " + entityType + " o").setReadOnly(true).list();
list = (List< ? >) CollectionUtils.select(list, PredicateUtils.uniquePredicate());
final int size = list.size();
log.info("Writing " + size + " objects");
for (final Iterator< ? > it = list.iterator(); it.hasNext();) {
final Object obj = it.next();
if (log.isDebugEnabled()) {
log.debug("loaded object " + obj);
}
Hibernate.initialize(obj);
final Class< ? > targetClass = HibernateProxyHelper.getClassWithoutInitializingProxy(obj);
final ClassMetadata classMetadata = session.getSessionFactory().getClassMetadata(targetClass);
if (classMetadata == null) {
log.fatal("Can't init " + obj + " of type " + targetClass);
continue;
}
// initalisierung des Objekts...
defaultXStream.marshal(obj, new CompactWriter(new NullWriter()));
if (preserveIds == false) {
// Nun kann die ID gelöscht werden
classMetadata.setIdentifier(obj, null, EntityMode.POJO);
}
if (log.isDebugEnabled()) {
log.debug("loading evicted object " + obj);
}
if (this.ignoreFromTopLevelListing.contains(targetClass) == false) {
all.add(obj);
}
}
}
// und schreiben
try {
writer.write("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n");
} catch (final IOException ex) {
// ignore, will fail on stream.marshal()
}
log.info("Wrote " + all.size() + " objects");
final MarshallingStrategy marshallingStrategy = new ProxyIdRefMarshallingStrategy();
stream.setMarshallingStrategy(marshallingStrategy);
stream.marshal(all, new PrettyPrintWriter(writer));
}
/**
* Overload this method if you need further initializations before reading xml stream. Does nothing at default.
* @param xstream
*/
protected void init(final XStream xstream)
{
}
/**
* @return
*/
private XStream initXStream(final Session session, final boolean nullifyPk)
{
final XStream xstream = new XStream() {
@Override
protected MapperWrapper wrapMapper(final MapperWrapper next)
{
return new HibernateMapper(new HibernateCollectionsMapper(next));
}
};
// Converter für die Hibernate-Collections
xstream.registerConverter(new HibernateCollectionConverter(xstream.getConverterLookup()));
xstream.registerConverter(
new HibernateProxyConverter(xstream.getMapper(), new PureJavaReflectionProvider(), xstream.getConverterLookup()),
XStream.PRIORITY_VERY_HIGH);
xstream.setMarshallingStrategy(new XStreamMarshallingStrategy(XStreamMarshallingStrategy.RELATIVE));
init(xstream);
return xstream;
}
}