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.ByteArrayInputStream; 027 import java.io.IOException; 028 import java.io.InputStream; 029 import java.util.Collections; 030 import java.util.HashSet; 031 import java.util.LinkedList; 032 import java.util.List; 033 import java.util.Set; 034 import org.jboss.dna.common.collection.Problems; 035 import org.jboss.dna.graph.JcrLexicon; 036 import org.jboss.dna.graph.JcrNtLexicon; 037 import org.jboss.dna.graph.Node; 038 import org.jboss.dna.graph.observe.NetChangeObserver.NetChange; 039 import org.jboss.dna.graph.property.Binary; 040 import org.jboss.dna.graph.property.Path; 041 import org.jboss.dna.graph.property.PathFactory; 042 import org.jboss.dna.graph.property.PathNotFoundException; 043 import org.jboss.dna.graph.property.Property; 044 import org.jboss.dna.graph.property.PropertyFactory; 045 import org.jboss.dna.graph.property.ValueFactories; 046 import org.jboss.dna.graph.sequencer.StreamSequencer; 047 import org.jboss.dna.graph.sequencer.StreamSequencerContext; 048 import org.jboss.dna.repository.RepositoryI18n; 049 import org.jboss.dna.repository.util.RepositoryNodePath; 050 051 /** 052 * An adapter class that wraps a {@link StreamSequencer} instance to be a {@link Sequencer}. 053 * 054 * @author Randall Hauch 055 * @author John Verhaeg 056 */ 057 public class StreamSequencerAdapter implements Sequencer { 058 059 private SequencerConfig configuration; 060 private final StreamSequencer streamSequencer; 061 062 public StreamSequencerAdapter( StreamSequencer streamSequencer ) { 063 this.streamSequencer = streamSequencer; 064 } 065 066 /** 067 * {@inheritDoc} 068 */ 069 public SequencerConfig getConfiguration() { 070 return this.configuration; 071 } 072 073 /** 074 * {@inheritDoc} 075 */ 076 public void setConfiguration( SequencerConfig configuration ) { 077 this.configuration = configuration; 078 } 079 080 /** 081 * {@inheritDoc} 082 */ 083 public void execute( Node input, 084 String sequencedPropertyName, 085 NetChange changes, 086 Set<RepositoryNodePath> outputPaths, 087 SequencerContext context, 088 Problems problems ) throws SequencerException { 089 // 'sequencedPropertyName' contains the name of the modified property on 'input' that resulted in the call to this 090 // sequencer. 091 // 'changes' contains all of the changes to this node that occurred in the transaction. 092 // 'outputPaths' contains the paths of the node(s) where this sequencer is to save it's data. 093 094 // Get the property that contains the data, given by 'propertyName' ... 095 Property sequencedProperty = input.getProperty(sequencedPropertyName); 096 097 if (sequencedProperty == null || sequencedProperty.isEmpty()) { 098 String msg = RepositoryI18n.unableToFindPropertyForSequencing.text(sequencedPropertyName, input.getLocation()); 099 throw new SequencerException(msg); 100 } 101 102 // Get the binary property with the image content, and build the image metadata from the image ... 103 ValueFactories factories = context.getExecutionContext().getValueFactories(); 104 SequencerOutputMap output = new SequencerOutputMap(factories); 105 InputStream stream = null; 106 Throwable firstError = null; 107 Binary binary = factories.getBinaryFactory().create(sequencedProperty.getFirstValue()); 108 binary.acquire(); 109 try { 110 // Parallel the JCR lemma for converting objects into streams 111 stream = binary.getStream(); 112 StreamSequencerContext StreamSequencerContext = createStreamSequencerContext(input, 113 sequencedProperty, 114 context, 115 problems); 116 this.streamSequencer.sequence(stream, output, StreamSequencerContext); 117 } catch (Throwable t) { 118 // Record the error ... 119 firstError = t; 120 } finally { 121 try { 122 if (stream != null) { 123 // Always close the stream, recording the error if we've not yet seen an error 124 try { 125 stream.close(); 126 } catch (Throwable t) { 127 if (firstError == null) firstError = t; 128 } finally { 129 stream = null; 130 } 131 } 132 if (firstError != null) { 133 // Wrap and throw the first error that we saw ... 134 throw new SequencerException(firstError); 135 } 136 } finally { 137 binary.release(); 138 } 139 } 140 141 // Accumulator of paths that we've added to the batch but have not yet been submitted to the graph 142 Set<Path> builtPaths = new HashSet<Path>(); 143 144 // Find each output node and save the image metadata there ... 145 for (RepositoryNodePath outputPath : outputPaths) { 146 // Get the name of the repository workspace and the path to the output node 147 final String repositoryWorkspaceName = outputPath.getWorkspaceName(); 148 final String nodePath = outputPath.getNodePath(); 149 150 // Find or create the output node in this session ... 151 context.graph().useWorkspace(repositoryWorkspaceName); 152 153 buildPathTo(nodePath, context, builtPaths); 154 // Node outputNode = context.graph().getNodeAt(nodePath); 155 156 // Now save the image metadata to the output node ... 157 saveOutput(nodePath, output, context, builtPaths); 158 } 159 160 context.getDestination().submit(); 161 } 162 163 /** 164 * Creates all nodes along the given node path if they are missing. Ensures that nodePath is a valid path to a node. 165 * 166 * @param nodePath the node path to create 167 * @param context the sequencer context under which it should be created 168 * @param builtPaths a set of the paths that have already been created but not submitted in this batch 169 */ 170 private void buildPathTo( String nodePath, 171 SequencerContext context, 172 Set<Path> builtPaths ) { 173 PathFactory pathFactory = context.getExecutionContext().getValueFactories().getPathFactory(); 174 Path targetPath = pathFactory.create(nodePath); 175 176 buildPathTo(targetPath, context, builtPaths); 177 } 178 179 /** 180 * Creates all nodes along the given node path if they are missing. Ensures that nodePath is a valid path to a node. 181 * 182 * @param targetPath the node path to create 183 * @param context the sequencer context under which it should be created 184 * @param builtPaths a set of the paths that have already been created but not submitted in this batch 185 */ 186 private void buildPathTo( Path targetPath, 187 SequencerContext context, 188 Set<Path> builtPaths ) { 189 PathFactory pathFactory = context.getExecutionContext().getValueFactories().getPathFactory(); 190 PropertyFactory propFactory = context.getExecutionContext().getPropertyFactory(); 191 192 if (targetPath.isRoot()) return; 193 Path workingPath = pathFactory.createRootPath(); 194 Path.Segment[] segments = targetPath.getSegmentsArray(); 195 int i = 0; 196 Property primaryType = propFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.UNSTRUCTURED); 197 for (int max = segments.length; i < max; i++) { 198 workingPath = pathFactory.create(workingPath, segments[i]); 199 200 if (!builtPaths.contains(workingPath)) { 201 try { 202 context.graph().getNodeAt(workingPath); 203 } catch (PathNotFoundException pnfe) { 204 context.getDestination().create(workingPath, primaryType); 205 builtPaths.add(workingPath); 206 } 207 } 208 } 209 } 210 211 /** 212 * Save the sequencing output to the supplied node. This method does not need to save the output, as that is done by the 213 * caller of this method. 214 * 215 * @param nodePath the existing node onto (or below) which the output is to be written; never null 216 * @param output the (immutable) sequencing output; never null 217 * @param context the execution context for this sequencing operation; never null 218 * @param builtPaths a set of the paths that have already been created but not submitted in this batch 219 */ 220 protected void saveOutput( String nodePath, 221 SequencerOutputMap output, 222 SequencerContext context, 223 Set<Path> builtPaths ) { 224 if (output.isEmpty()) return; 225 final PathFactory pathFactory = context.getExecutionContext().getValueFactories().getPathFactory(); 226 final PropertyFactory propertyFactory = context.getExecutionContext().getPropertyFactory(); 227 final Path outputNodePath = pathFactory.create(nodePath); 228 229 // Iterate over the entries in the output, in Path's natural order (shorter paths first and in lexicographical order by 230 // prefix and name) 231 for (SequencerOutputMap.Entry entry : output) { 232 Path targetNodePath = entry.getPath(); 233 234 // Resolve this path relative to the output node path, handling any parent or self references ... 235 Path absolutePath = targetNodePath.isAbsolute() ? targetNodePath : outputNodePath.resolve(targetNodePath); 236 237 List<Property> properties = new LinkedList<Property>(); 238 // Set all of the properties on this 239 for (SequencerOutputMap.PropertyValue property : entry.getPropertyValues()) { 240 properties.add(propertyFactory.create(property.getName(), property.getValue())); 241 // TODO: Handle reference properties - currently passed in as Paths 242 } 243 244 if (absolutePath.getParent() != null) { 245 buildPathTo(absolutePath.getParent(), context, builtPaths); 246 } 247 context.getDestination().create(absolutePath, properties); 248 builtPaths.add(absolutePath); 249 } 250 } 251 252 protected String[] extractMixinTypes( Object value ) { 253 if (value instanceof String[]) return (String[])value; 254 if (value instanceof String) return new String[] {(String)value}; 255 return null; 256 } 257 258 protected StreamSequencerContext createStreamSequencerContext( Node input, 259 Property sequencedProperty, 260 SequencerContext context, 261 Problems problems ) { 262 assert input != null; 263 assert sequencedProperty != null; 264 assert context != null; 265 assert problems != null; 266 ValueFactories factories = context.getExecutionContext().getValueFactories(); 267 Path path = factories.getPathFactory().create(input.getLocation().getPath()); 268 269 Set<org.jboss.dna.graph.property.Property> props = new HashSet<org.jboss.dna.graph.property.Property>( 270 input.getPropertiesByName() 271 .values()); 272 props = Collections.unmodifiableSet(props); 273 String mimeType = getMimeType(context, sequencedProperty, path.getLastSegment().getName().getLocalName()); 274 return new StreamSequencerContext(context.getExecutionContext(), path, props, mimeType, problems); 275 } 276 277 protected String getMimeType( SequencerContext context, 278 Property sequencedProperty, 279 String name ) { 280 SequencerException err = null; 281 String mimeType = null; 282 InputStream stream = null; 283 try { 284 // Parallel the JCR lemma for converting objects into streams 285 stream = new ByteArrayInputStream(sequencedProperty.toString().getBytes()); 286 mimeType = context.getExecutionContext().getMimeTypeDetector().mimeTypeOf(name, stream); 287 return mimeType; 288 } catch (Exception error) { 289 err = new SequencerException(error); 290 } finally { 291 if (stream != null) { 292 try { 293 stream.close(); 294 } catch (IOException error) { 295 // Only throw exception if an exception was not already thrown 296 if (err == null) err = new SequencerException(error); 297 } 298 } 299 } 300 throw err; 301 } 302 }