001
002 /*
003 * Copyright (C) 2012 Archie L. Cobbs. All rights reserved.
004 *
005 * $Id: SpringPersistentObjectSchemaUpdater.java 269 2012-02-01 23:20:34Z archie.cobbs $
006 */
007
008 package org.dellroad.stuff.pobj;
009
010 import java.io.BufferedInputStream;
011 import java.io.IOException;
012 import java.io.InputStream;
013 import java.util.Collection;
014 import java.util.Comparator;
015
016 import javax.xml.transform.stream.StreamSource;
017
018 import org.dellroad.stuff.schema.SchemaUpdate;
019 import org.dellroad.stuff.spring.BeanNameComparator;
020 import org.springframework.beans.factory.BeanFactory;
021 import org.springframework.beans.factory.BeanFactoryAware;
022 import org.springframework.beans.factory.DisposableBean;
023 import org.springframework.beans.factory.InitializingBean;
024 import org.springframework.beans.factory.ListableBeanFactory;
025 import org.springframework.core.io.Resource;
026
027 /**
028 * {@link PersistentObjectSchemaUpdater} optimized for use with Spring:
029 * <ul>
030 * <li>{@link #getOrderingTieBreaker} is overridden to break ties by ordering updates in the same order
031 * as they are defined in the bean factory.</li>
032 * <li>This class implements {@link InitializingBean} and verifies all required properties are set.</li>
033 * <li>If no updates are {@linkplain #setUpdates explicitly configured}, then all {@link SpringPersistentObjectSchemaUpdate}s
034 * found in the containing bean factory are automatically configured; this requires that all of the schema updates
035 * are defined in the same {@link ListableBeanFactory}.</li>
036 * </ul>
037 *
038 * <p>
039 * An example of how this class can be combined with custom XML to define an updater and all its updates:
040 * <blockquote><pre>
041 * <beans xmlns="http://www.springframework.org/schema/beans"
042 * xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
043 * xmlns:p="http://www.springframework.org/schema/p"
044 * xsi:schemaLocation="
045 * http://www.springframework.org/schema/beans
046 * http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
047 *
048 * <!-- Persistent object delegate; you supply the XML (un)marshaller -->
049 * <b><bean id="delegate" class="org.dellroad.stuff.pobj.SpringDelegate"
050 * p:marshaller-ref="marshaller" p:unmarshaller-ref="unmarshaller"/></b>
051 *
052 * <!-- Persistent object schema updater -->
053 * <b><bean id="schemaUpdater" class="org.dellroad.stuff.pobj.SpringPersistentObjectSchemaUpdater"
054 * p:file="/var/example/pobj.xml" p:delegate-ref="delegate"
055 * p:initialXML="classpath:com/example/initial-pobj.xml"/></b>
056 *
057 * <!-- Persistent object bean -->
058 * <b><bean scope="prototype" factory-bean="schemaUpdater" factory-method="getPersistentObject"/></b>
059 *
060 * <!-- Define a common location for our schema update XSLTs -->
061 * <b><bean class="org.dellroad.stuff.pobj.SpringXSLUpdateTransformConfigurer"
062 * p:prefix="classpath:updates/" p:suffix=".xsl"/></b>
063 *
064 * <!-- Schema update #1 -->
065 * <b><bean id="update1" class="org.dellroad.stuff.pobj.SpringXSLPersistentObjectSchemaUpdate"
066 * transform="file:///usr/share/updates/SomeUpdate.xsl"/></b>
067 *
068 * <!-- Schema update #2: uses "classpath:updates/update2.xsl" thanks to TransformConfigurer -->
069 * <b><bean id="update2" class="org.dellroad.stuff.pobj.SpringXSLPersistentObjectSchemaUpdate"/></b>
070 *
071 * <!-- Add more schema updates over time as needed and everything just works... -->
072 *
073 * </beans>
074 * </pre></blockquote>
075 *
076 * <p>
077 * The {@link PersistentObject} itself, fully updated, is accessible via {@link #getPersistentObject}.
078 * or, in the above example, via the <code>persistentObject</code> bean.
079 *
080 * @param <T> type of the root persistent object
081 */
082 public class SpringPersistentObjectSchemaUpdater<T> extends PersistentObjectSchemaUpdater<T>
083 implements BeanFactoryAware, InitializingBean, DisposableBean {
084
085 private ListableBeanFactory beanFactory;
086 private T initialValue;
087 private Resource initialXML;
088
089 /**
090 * Set the initial value to be used on an uninitialized persistent object.
091 *
092 * <p>
093 * Either this or a {@linkplain #setInitialXML initial XML resource} should be configured
094 * to handle the case that no persistent file exists yet.
095 */
096 public void setInitialValue(T initialValue) {
097 this.initialValue = initialValue;
098 }
099
100 /**
101 * Set the resource containing the initial value, encoded as XML, to be used on an uninitialized persistent object.
102 * This can be used as an alternative to {@link #setInitialValue}.
103 *
104 * <p>
105 * Either this or an explicit {@linkplain #setInitialValue initial value} should be configured
106 * to handle the case that no persistent file exists yet.
107 */
108 public void setInitialXML(Resource resource) {
109 this.initialXML = resource;
110 }
111
112 /**
113 * Get the initial value for the persistent object when no persistent file is found.
114 *
115 * <p>
116 * The implementation in {@link SpringPersistentObjectSchemaUpdater} returns the {@linkplain #setInitialValue initial value},
117 * if any, otherwise it falls back to decoding the initial value from the {@linkplain #setInitialXML initial value
118 * resource}, if any. If neither property is configured, null is returned.
119 */
120 @Override
121 protected T getInitialValue() {
122
123 // If value is provided explicitly, just return it
124 if (this.initialValue != null) {
125 this.log.info("loading initial content from explicitly configured value");
126 return this.initialValue;
127 }
128
129 // Use configured XML if available
130 if (this.initialXML == null)
131 return null;
132 try {
133 this.log.info("loading initial content from " + this.initialXML.getURI());
134 InputStream input = this.initialXML.getInputStream();
135 try {
136 return this.delegate.deserialize(
137 new StreamSource(new BufferedInputStream(input), this.initialXML.getURI().toString()));
138 } finally {
139 try {
140 input.close();
141 } catch (IOException e) {
142 // ignore
143 }
144 }
145 } catch (RuntimeException e) {
146 throw e;
147 } catch (Exception e) {
148 throw new PersistentObjectException(e);
149 }
150 }
151
152 @Override
153 public void setBeanFactory(BeanFactory beanFactory) {
154 if (beanFactory instanceof ListableBeanFactory)
155 this.beanFactory = (ListableBeanFactory)beanFactory;
156 }
157
158 @Override
159 @SuppressWarnings("unchecked")
160 public void afterPropertiesSet() throws Exception {
161
162 // Check config
163 if (this.file == null)
164 throw new IllegalArgumentException("no file configured");
165 if (this.writeDelay < 0)
166 throw new IllegalArgumentException("negative writeDelay file configured");
167 if (this.delegate == null)
168 throw new IllegalArgumentException("no delegate configured");
169 if (this.getUpdates() == null) {
170 if (this.beanFactory == null) {
171 throw new IllegalArgumentException("no updates explicitly configured and the containing BeanFactory"
172 + " is not a ListableBeanFactory: " + this.beanFactory);
173 }
174 this.setUpdates((Collection<SpringPersistentObjectSchemaUpdate<T>>)(Object)this.beanFactory.getBeansOfType(
175 SpringPersistentObjectSchemaUpdate.class).values());
176 }
177
178 // Start
179 this.start();
180 }
181
182 @Override
183 public void destroy() {
184 this.stop();
185 }
186
187 /**
188 * Get the preferred ordering of two updates that do not have any predecessor constraints
189 * (including implied indirect constraints) between them.
190 *
191 * <p>
192 * In the case no schema updates are explicitly configured, the {@link Comparator} returned by the
193 * implementation in {@link SpringPersistentObjectSchemaUpdater} sorts updates in the same order that they appear
194 * in the containing {@link ListableBeanFactory}. Otherwise, the
195 * {@linkplain org.dellroad.stuff.schema.AbstractSchemaUpdater#getOrderingTieBreaker superclass method} is used.
196 */
197 @Override
198 protected Comparator<SchemaUpdate<PersistentFileTransaction>> getOrderingTieBreaker() {
199 if (this.beanFactory == null)
200 return super.getOrderingTieBreaker();
201 final BeanNameComparator beanNameComparator = new BeanNameComparator(this.beanFactory);
202 return new Comparator<SchemaUpdate<PersistentFileTransaction>>() {
203 @Override
204 public int compare(SchemaUpdate<PersistentFileTransaction> update1, SchemaUpdate<PersistentFileTransaction> update2) {
205 return beanNameComparator.compare(update1.getName(), update2.getName());
206 }
207 };
208 }
209 }
210