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 }