All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.dellroad.stuff.pobj.PersistentObjectSchemaUpdater Maven / Gradle / Ivy


/*
 * Copyright (C) 2012 Archie L. Cobbs. All rights reserved.
 */

package org.dellroad.stuff.pobj;

import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stax.StAXResult;
import javax.xml.transform.stream.StreamResult;

import org.dellroad.stuff.schema.AbstractSchemaUpdater;
import org.dellroad.stuff.schema.UnrecognizedUpdateException;

/**
 * A {@link PersistentObjectDelegate} that is also a {@link AbstractSchemaUpdater} that automatically
 * applies needed updates to the persistent XML file.
 *
 * 

* To use this class, wrap your normal delegate in an instance of this class. This will augment * the serialization and deserialization process to keep track of which updates have been applied to the XML structure, * and automatically and transparently apply any needed updates during deserialization. * *

* Updates are tracked by inserting an {@link #UPDATES_ELEMENT_NAME <pobj:updates>} element * into the serialized XML document; this update list is transparently removed when the document is read back, * and any missing updates are applied automatically. * In this way the document and its set of applied updates always travel together. For example: * *

 *  <MyConfig>
 *      <pobj:updates xmlns:pobj="http://dellroad-stuff.googlecode.com/ns/persistentObject">
 *          <pobj:update>some-update-name-1</pobj:update>
 *          <pobj:update>some-update-name-2</pobj:update>
 *      </pobj:updates>
 *      <username>admin</username>
 *      <password>secret</password>
 *  </MyConfig>
 * 
* *

* For Spring applications, {@link SpringPersistentObjectSchemaUpdater} provides a convenient declarative way * to define your schema updates via XSLT files. * * @param type of the root persistent object */ public class PersistentObjectSchemaUpdater extends AbstractSchemaUpdater implements PersistentObjectDelegate { /** * XML namespace URI used for nested update elements. */ public static final String NAMESPACE_URI = "http://dellroad-stuff.googlecode.com/ns/persistentObject"; /** * Preferred XML namespace prefix for {@link #NAMESPACE_URI} elements. */ public static final String XML_PREFIX = "pobj"; /** * XML element name for the updates list. */ public static final QName UPDATES_ELEMENT_NAME = new QName(NAMESPACE_URI, "updates", XML_PREFIX); /** * XML element name for a single update. */ public static final QName UPDATE_ELEMENT_NAME = new QName(NAMESPACE_URI, "update", XML_PREFIX); /** * XML namespace URI used for namespace declarations. */ public static final QName XMLNS_ATTRIBUTE_NAME = new QName("http://www.w3.org/2000/xmlns/", XML_PREFIX, "xmlns"); protected final PersistentObjectDelegate delegate; private final XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newFactory(); private List updateNames; private TransformerFactory transformerFactory; // Constructors /** * Constructor. * *

* Callers provide a {@link PersistentObjectDelegate} that will be used for all operations, with the exception * that {@linkplain #serialize serialization} and {@linkplain #deserialize deserialization} will add/remove * the upate list, and {@linkplain #deserialize deserialization} will apply any needed updates as necessary. * * @param delegate delegate that will be wrapped by this instance * @throws IllegalArgumentException if {@code delegate} is null */ public PersistentObjectSchemaUpdater(PersistentObjectDelegate delegate) { if (delegate == null) throw new IllegalArgumentException("null delegate"); this.delegate = delegate; } // Accessors /** * Configure the {@link TransformerFactory} to be used by this instance when reading the updates from the XML file. * Must support reading from a StAX {@link Source}. * *

* The implementation in {@link PersistentObjectSchemaUpdater} returns null. * * @param transformerFactory {@link TransformerFactory} to use or null for the platform default */ public void setTransformerFactory(TransformerFactory transformerFactory) { this.transformerFactory = transformerFactory; } // PersistentObjectDelegate methods /** * Make a deep copy of the given object. * *

* The implementation in {@link PersistentObjectSchemaUpdater} delegates to the delegate provided in the constructor. * * @throws IllegalArgumentException {@inheritDoc} * @throws PersistentObjectException {@inheritDoc} */ @Override public T copy(T original) { return this.delegate.copy(original); } /** * Compare two object graphs. * *

* The implementation in {@link PersistentObjectSchemaUpdater} delegates to the delegate provided in the constructor. */ @Override public boolean isSameGraph(T root1, T root2) { return this.delegate.isSameGraph(root1, root2); } /** * Validate the given instance. * *

* The implementation in {@link PersistentObjectSchemaUpdater} delegates to the delegate provided in the constructor. * * @throws IllegalArgumentException {@inheritDoc} */ @Override public Set> validate(T obj) { return this.delegate.validate(obj); } /** * Handle an exception thrown during a delayed write-back attempt. * *

* The implementation in {@link PersistentObjectSchemaUpdater} delegates to the delegate provided in the constructor. */ @Override public void handleWritebackException(PersistentObject pobj, Throwable t) { this.delegate.handleWritebackException(pobj, t); } /** * Get the default value for the root object graph. * *

* The implementation in {@link PersistentObjectSchemaUpdater} delegates to the delegate provided in the constructor. */ @Override public T getDefaultValue() { return this.delegate.getDefaultValue(); } /** * Serialize object to XML. * *

* The implementation in {@link PersistentObjectSchemaUpdater} delegates to the delegate provided in the constructor * but also adds the update list as the first XML tag. * * @throws PersistentObjectException {@inheritDoc} */ @Override public void serialize(T obj, Result result) throws IOException { try { // Get update names final List updateNameList = this.updateNames != null ? this.updateNames : this.getAllUpdateNames(); // Wrap result with a writer that adds the update list XMLStreamWriter writer; if (result instanceof StreamResult && ((StreamResult)result).getOutputStream() != null) writer = this.xmlOutputFactory.createXMLStreamWriter(((StreamResult)result).getOutputStream(), "UTF-8"); else writer = this.xmlOutputFactory.createXMLStreamWriter(result); final UpdatesXMLStreamWriter updatesWriter = new UpdatesXMLStreamWriter(writer, updateNameList); // Serialize using the provided delegate this.delegate.serialize(obj, new StAXResult(updatesWriter)); updatesWriter.close(); } catch (RuntimeException e) { throw e; } catch (IOException e) { throw e; } catch (Exception e) { throw new PersistentObjectException(e); } } /** * Deserialize object from XML. * *

* The implementation in {@link PersistentObjectSchemaUpdater} delegates to the delegate provided in the constructor * but also removes the update list as the first XML tag and applies any needed updates. * * @throws PersistentObjectException {@inheritDoc} */ @Override public T deserialize(Source source) throws IOException { try { // Create a temporary in-memory "database" containing the XML content PersistentFileTransaction transaction = new PersistentFileTransaction(source, this.transformerFactory); // Apply schema updates as necessary to update the XML structure this.initializeAndUpdateDatabase(transaction); // Save updates names so we can preserve their order this.updateNames = transaction.getUpdates(); // Sanity check that all updates were applied final HashSet unappliedUpdates = new HashSet(this.getAllUpdateNames()); unappliedUpdates.removeAll(this.updateNames); if (!unappliedUpdates.isEmpty()) throw new PersistentObjectException("internal inconsistency: unapplied updates remain: " + unappliedUpdates); // Deserialize the now up-to-date XML structure using the provided delegate return this.delegate.deserialize(new DOMSource(transaction.getData(), transaction.getSystemId())); } catch (UnrecognizedUpdateException e) { throw new PersistentObjectException(e); } catch (RuntimeException e) { throw e; } catch (IOException e) { throw e; } catch (Exception e) { throw new PersistentObjectException(e); } } // AbstractSchemaUpdater methods @Override protected boolean databaseNeedsInitialization(PersistentFileTransaction transaction) throws Exception { return false; } @Override protected void initializeDatabase(PersistentFileTransaction transaction) throws Exception { throw new UnsupportedOperationException(); } @Override protected PersistentFileTransaction openTransaction(PersistentFileTransaction transaction) throws Exception { return transaction; } @Override protected void commitTransaction(PersistentFileTransaction transaction) throws Exception { // nothing to do } @Override protected void rollbackTransaction(PersistentFileTransaction transaction) throws Exception { // nothing to do } @Override protected Set getAppliedUpdateNames(PersistentFileTransaction transaction) throws Exception { return new HashSet(transaction.getUpdates()); } @Override protected void recordUpdateApplied(PersistentFileTransaction transaction, String name) throws Exception { transaction.getUpdates().add(name); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy