001 /*
002 * JBoss DNA (http://www.jboss.org/dna)
003 * See the COPYRIGHT.txt file distributed with this work for information
004 * regarding copyright ownership. Some portions may be licensed
005 * to Red Hat, Inc. under one or more contributor license agreements.
006 * See the AUTHORS.txt file in the distribution for a full listing of
007 * individual contributors.
008 *
009 * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
010 * is licensed to you under the terms of the GNU Lesser General Public License as
011 * published by the Free Software Foundation; either version 2.1 of
012 * the License, or (at your option) any later version.
013 *
014 * JBoss DNA is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017 * Lesser General Public License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this software; if not, write to the Free
021 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023 */
024 package org.jboss.dna.repository.sequencer;
025
026 import java.io.IOException;
027 import java.io.InputStream;
028 import java.math.BigDecimal;
029 import java.util.Calendar;
030 import java.util.Collections;
031 import java.util.Date;
032 import java.util.HashSet;
033 import java.util.Set;
034 import javax.jcr.Node;
035 import javax.jcr.PathNotFoundException;
036 import javax.jcr.Property;
037 import javax.jcr.PropertyIterator;
038 import javax.jcr.PropertyType;
039 import javax.jcr.RepositoryException;
040 import javax.jcr.Session;
041 import javax.jcr.Value;
042 import org.jboss.dna.common.collection.Problems;
043 import org.jboss.dna.common.util.Logger;
044 import org.jboss.dna.graph.ExecutionContext;
045 import org.jboss.dna.graph.property.Binary;
046 import org.jboss.dna.graph.property.DateTime;
047 import org.jboss.dna.graph.property.Name;
048 import org.jboss.dna.graph.property.NamespaceRegistry;
049 import org.jboss.dna.graph.property.Path;
050 import org.jboss.dna.graph.property.PathFactory;
051 import org.jboss.dna.graph.property.ValueFactories;
052 import org.jboss.dna.graph.sequencer.SequencerContext;
053 import org.jboss.dna.graph.sequencer.StreamSequencer;
054 import org.jboss.dna.repository.RepositoryI18n;
055 import org.jboss.dna.repository.mimetype.MimeType;
056 import org.jboss.dna.repository.observation.NodeChange;
057 import org.jboss.dna.repository.util.JcrExecutionContext;
058 import org.jboss.dna.repository.util.RepositoryNodePath;
059
060 /**
061 * An adapter class that wraps a {@link StreamSequencer} instance to be a {@link Sequencer}.
062 *
063 * @author Randall Hauch
064 * @author John Verhaeg
065 */
066 public class StreamSequencerAdapter implements Sequencer {
067
068 private SequencerConfig configuration;
069 private final StreamSequencer streamSequencer;
070
071 public StreamSequencerAdapter( StreamSequencer streamSequencer ) {
072 this.streamSequencer = streamSequencer;
073 }
074
075 /**
076 * {@inheritDoc}
077 */
078 public SequencerConfig getConfiguration() {
079 return this.configuration;
080 }
081
082 /**
083 * {@inheritDoc}
084 */
085 public void setConfiguration( SequencerConfig configuration ) {
086 this.configuration = configuration;
087 }
088
089 /**
090 * {@inheritDoc}
091 */
092 public void execute( Node input,
093 String sequencedPropertyName,
094 NodeChange changes,
095 Set<RepositoryNodePath> outputPaths,
096 JcrExecutionContext execContext,
097 Problems problems ) throws RepositoryException, SequencerException {
098 // 'sequencedPropertyName' contains the name of the modified property on 'input' that resulted in the call to this
099 // sequencer.
100 // 'changes' contains all of the changes to this node that occurred in the transaction.
101 // 'outputPaths' contains the paths of the node(s) where this sequencer is to save it's data.
102
103 // Get the property that contains the data, given by 'propertyName' ...
104 Property sequencedProperty = null;
105 try {
106 sequencedProperty = input.getProperty(sequencedPropertyName);
107 } catch (PathNotFoundException e) {
108 String msg = RepositoryI18n.unableToFindPropertyForSequencing.text(sequencedPropertyName, input.getPath());
109 throw new SequencerException(msg, e);
110 }
111
112 // Get the binary property with the image content, and build the image metadata from the image ...
113 SequencerOutputMap output = new SequencerOutputMap(execContext.getValueFactories());
114 InputStream stream = null;
115 Throwable firstError = null;
116 try {
117 stream = sequencedProperty.getStream();
118 SequencerContext sequencerContext = createSequencerContext(input, sequencedProperty, execContext, problems);
119 this.streamSequencer.sequence(stream, output, sequencerContext);
120 } catch (Throwable t) {
121 // Record the error ...
122 firstError = t;
123 } finally {
124 if (stream != null) {
125 // Always close the stream, recording the error if we've not yet seen an error
126 try {
127 stream.close();
128 } catch (Throwable t) {
129 if (firstError == null) firstError = t;
130 } finally {
131 stream = null;
132 }
133 }
134 if (firstError != null) {
135 // Wrap and throw the first error that we saw ...
136 throw new SequencerException(firstError);
137 }
138 }
139
140 // Find each output node and save the image metadata there ...
141 for (RepositoryNodePath outputPath : outputPaths) {
142 Session session = null;
143 try {
144 // Get the name of the repository workspace and the path to the output node
145 final String repositoryWorkspaceName = outputPath.getRepositoryWorkspaceName();
146 final String nodePath = outputPath.getNodePath();
147
148 // Create a session to the repository where the data should be written ...
149 session = execContext.getSessionFactory().createSession(repositoryWorkspaceName);
150
151 // Find or create the output node in this session ...
152 Node outputNode = execContext.getTools().findOrCreateNode(session, nodePath);
153
154 // Now save the image metadata to the output node ...
155 if (saveOutput(outputNode, output, execContext)) {
156 session.save();
157 }
158 } finally {
159 // Always close the session ...
160 if (session != null) session.logout();
161 }
162 }
163 }
164
165 /**
166 * Save the sequencing output to the supplied node. This method does not need to save the output, as that is done by the
167 * caller of this method.
168 *
169 * @param outputNode the existing node onto (or below) which the output is to be written; never null
170 * @param output the (immutable) sequencing output; never null
171 * @param context the execution context for this sequencing operation; never null
172 * @return true if the output was written to the node, or false if no information was written
173 * @throws RepositoryException
174 */
175 protected boolean saveOutput( Node outputNode,
176 SequencerOutputMap output,
177 JcrExecutionContext context ) throws RepositoryException {
178 if (output.isEmpty()) return false;
179 final PathFactory pathFactory = context.getValueFactories().getPathFactory();
180 final NamespaceRegistry namespaceRegistry = context.getNamespaceRegistry();
181 final Path outputNodePath = pathFactory.create(outputNode.getPath());
182 final Name jcrPrimaryTypePropertyName = context.getValueFactories().getNameFactory().create("jcr:primaryType");
183
184 // Iterate over the entries in the output, in Path's natural order (shorter paths first and in lexicographical order by
185 // prefix and name)
186 for (SequencerOutputMap.Entry entry : output) {
187 Path targetNodePath = entry.getPath();
188 Name primaryType = entry.getPrimaryTypeValue();
189
190 // Resolve this path relative to the output node path, handling any parent or self references ...
191 Path absolutePath = targetNodePath.isAbsolute() ? targetNodePath : outputNodePath.resolve(targetNodePath);
192 Path relativePath = absolutePath.relativeTo(outputNodePath);
193
194 // Find or add the node (which may involve adding intermediate nodes) ...
195 Node targetNode = outputNode;
196 for (int i = 0, max = relativePath.size(); i != max; ++i) {
197 Path.Segment segment = relativePath.getSegment(i);
198 String qualifiedName = segment.getString(namespaceRegistry);
199 if (targetNode.hasNode(qualifiedName)) {
200 targetNode = targetNode.getNode(qualifiedName);
201 } else {
202 // It doesn't exist, so create it ...
203 if (segment.hasIndex()) {
204 // Use a name without an index ...
205 qualifiedName = segment.getName().getString(namespaceRegistry);
206 }
207 // We only have the primary type for the final one ...
208 if (i == (max - 1) && primaryType != null) {
209 targetNode = targetNode.addNode(qualifiedName, primaryType.getString(namespaceRegistry,
210 Path.NO_OP_ENCODER));
211 } else {
212 targetNode = targetNode.addNode(qualifiedName);
213 }
214 }
215 assert targetNode != null;
216 }
217 assert targetNode != null;
218
219 // Set all of the properties on this
220 for (SequencerOutputMap.PropertyValue property : entry.getPropertyValues()) {
221 String propertyName = property.getName().getString(namespaceRegistry, Path.NO_OP_ENCODER);
222 Object value = property.getValue();
223 if (jcrPrimaryTypePropertyName.equals(property.getName())) {
224 // Skip the primary type property (which is protected in Jackrabbit 1.5)
225 Logger.getLogger(this.getClass()).trace("Skipping property {0}/{1}={2}",
226 targetNode.getPath(),
227 propertyName,
228 value);
229 continue;
230 }
231 Logger.getLogger(this.getClass()).trace("Writing property {0}/{1}={2}", targetNode.getPath(), propertyName, value);
232 if (value instanceof Boolean) {
233 targetNode.setProperty(propertyName, ((Boolean)value).booleanValue());
234 } else if (value instanceof String) {
235 targetNode.setProperty(propertyName, (String)value);
236 } else if (value instanceof String[]) {
237 targetNode.setProperty(propertyName, (String[])value);
238 } else if (value instanceof Integer) {
239 targetNode.setProperty(propertyName, ((Integer)value).intValue());
240 } else if (value instanceof Short) {
241 targetNode.setProperty(propertyName, ((Short)value).shortValue());
242 } else if (value instanceof Long) {
243 targetNode.setProperty(propertyName, ((Long)value).longValue());
244 } else if (value instanceof Float) {
245 targetNode.setProperty(propertyName, ((Float)value).floatValue());
246 } else if (value instanceof Double) {
247 targetNode.setProperty(propertyName, ((Double)value).doubleValue());
248 } else if (value instanceof Binary) {
249 Binary binaryValue = (Binary)value;
250 try {
251 binaryValue.acquire();
252 targetNode.setProperty(propertyName, binaryValue.getStream());
253 } finally {
254 binaryValue.release();
255 }
256 } else if (value instanceof BigDecimal) {
257 targetNode.setProperty(propertyName, ((BigDecimal)value).doubleValue());
258 } else if (value instanceof DateTime) {
259 targetNode.setProperty(propertyName, ((DateTime)value).toCalendar());
260 } else if (value instanceof Date) {
261 DateTime instant = context.getValueFactories().getDateFactory().create((Date)value);
262 targetNode.setProperty(propertyName, instant.toCalendar());
263 } else if (value instanceof Calendar) {
264 targetNode.setProperty(propertyName, (Calendar)value);
265 } else if (value instanceof Name) {
266 Name nameValue = (Name)value;
267 String stringValue = nameValue.getString(namespaceRegistry);
268 targetNode.setProperty(propertyName, stringValue);
269 } else if (value instanceof Path) {
270 // Find the path to reference node ...
271 Path pathToReferencedNode = (Path)value;
272 if (!pathToReferencedNode.isAbsolute()) {
273 // Resolve the path relative to the output node ...
274 pathToReferencedNode = outputNodePath.resolve(pathToReferencedNode);
275 }
276 // Find the referenced node ...
277 try {
278 Node referencedNode = outputNode.getNode(pathToReferencedNode.getString());
279 targetNode.setProperty(propertyName, referencedNode);
280 } catch (PathNotFoundException e) {
281 String msg = RepositoryI18n.errorGettingNodeRelativeToNode.text(value, outputNode.getPath());
282 throw new SequencerException(msg, e);
283 }
284 } else if (value == null) {
285 // Remove the property ...
286 targetNode.setProperty(propertyName, (String)null);
287 } else {
288 String msg = RepositoryI18n.unknownPropertyValueType.text(value, value.getClass().getName());
289 throw new SequencerException(msg);
290 }
291 }
292 }
293
294 return true;
295 }
296
297 protected String[] extractMixinTypes( Object value ) {
298 if (value instanceof String[]) return (String[])value;
299 if (value instanceof String) return new String[] {(String)value};
300 return null;
301 }
302
303 protected SequencerContext createSequencerContext( Node input,
304 Property sequencedProperty,
305 ExecutionContext context,
306 Problems problems ) throws RepositoryException {
307 assert input != null;
308 assert sequencedProperty != null;
309 assert context != null;
310 assert problems != null;
311 // Translate JCR path and property values to DNA constructs and cache them to improve performance and prevent
312 // RepositoryException from being thrown by getters
313 // Note: getMimeType() will still operate lazily, and thus throw a SequencerException, since it is very intrusive and
314 // potentially slow-running.
315 ValueFactories factories = context.getValueFactories();
316 Path path = factories.getPathFactory().create(input.getPath());
317 Set<org.jboss.dna.graph.property.Property> props = new HashSet<org.jboss.dna.graph.property.Property>();
318 for (PropertyIterator iter = input.getProperties(); iter.hasNext();) {
319 javax.jcr.Property jcrProp = iter.nextProperty();
320 org.jboss.dna.graph.property.Property prop;
321 if (jcrProp.getDefinition().isMultiple()) {
322 Value[] jcrVals = jcrProp.getValues();
323 Object[] vals = new Object[jcrVals.length];
324 int ndx = 0;
325 for (Value jcrVal : jcrVals) {
326 vals[ndx++] = convert(factories, jcrProp.getName(), jcrVal);
327 }
328 prop = context.getPropertyFactory().create(factories.getNameFactory().create(jcrProp.getName()), vals);
329 } else {
330 Value jcrVal = jcrProp.getValue();
331 Object val = convert(factories, jcrProp.getName(), jcrVal);
332 prop = context.getPropertyFactory().create(factories.getNameFactory().create(jcrProp.getName()), val);
333 }
334 props.add(prop);
335 }
336 props = Collections.unmodifiableSet(props);
337 String mimeType = getMimeType(sequencedProperty, path.getLastSegment().getName().getLocalName());
338 return new SequencerContext(context, path, props, mimeType, problems);
339 }
340
341 protected Object convert( ValueFactories factories,
342 String name,
343 Value jcrValue ) throws RepositoryException {
344 switch (jcrValue.getType()) {
345 case PropertyType.BINARY: {
346 return factories.getBinaryFactory().create(jcrValue.getStream());
347 }
348 case PropertyType.BOOLEAN: {
349 return factories.getBooleanFactory().create(jcrValue.getBoolean());
350 }
351 case PropertyType.DATE: {
352 return factories.getDateFactory().create(jcrValue.getDate());
353 }
354 case PropertyType.DOUBLE: {
355 return factories.getDoubleFactory().create(jcrValue.getDouble());
356 }
357 case PropertyType.LONG: {
358 return factories.getLongFactory().create(jcrValue.getLong());
359 }
360 case PropertyType.NAME: {
361 return factories.getNameFactory().create(jcrValue.getString());
362 }
363 case PropertyType.PATH: {
364 return factories.getPathFactory().create(jcrValue.getString());
365 }
366 case PropertyType.REFERENCE: {
367 return factories.getReferenceFactory().create(jcrValue.getString());
368 }
369 case PropertyType.STRING: {
370 return factories.getStringFactory().create(jcrValue.getString());
371 }
372 default: {
373 throw new RepositoryException(RepositoryI18n.unknownPropertyValueType.text(name, jcrValue.getType()));
374 }
375 }
376 }
377
378 @SuppressWarnings( "null" )
379 // The need for the SuppressWarnings looks like an Eclipse bug
380 protected String getMimeType( Property sequencedProperty,
381 String name ) {
382 SequencerException err = null;
383 String mimeType = null;
384 InputStream stream = null;
385 try {
386 stream = sequencedProperty.getStream();
387 mimeType = MimeType.of(name, stream);
388 return mimeType;
389 } catch (Exception error) {
390 err = new SequencerException(error);
391 } finally {
392 if (stream != null) {
393 try {
394 stream.close();
395 } catch (IOException error) {
396 // Only throw exception if an exception was not already thrown
397 if (err == null) err = new SequencerException(error);
398 }
399 }
400 }
401 if (err != null) throw err;
402 return mimeType;
403 }
404 }