View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors. 
8    *
9    * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10   * is licensed to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   *
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23   */
24  package org.modeshape.repository.sequencer;
25  
26  import java.io.ByteArrayInputStream;
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.util.HashSet;
30  import java.util.LinkedList;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Set;
34  import org.modeshape.common.collection.Collections;
35  import org.modeshape.common.collection.Problems;
36  import org.modeshape.common.util.Logger;
37  import org.modeshape.common.util.Reflection;
38  import org.modeshape.graph.JcrLexicon;
39  import org.modeshape.graph.JcrNtLexicon;
40  import org.modeshape.graph.Node;
41  import org.modeshape.graph.observe.NetChangeObserver.NetChange;
42  import org.modeshape.graph.property.Binary;
43  import org.modeshape.graph.property.Name;
44  import org.modeshape.graph.property.Path;
45  import org.modeshape.graph.property.PathFactory;
46  import org.modeshape.graph.property.PathNotFoundException;
47  import org.modeshape.graph.property.Property;
48  import org.modeshape.graph.property.PropertyFactory;
49  import org.modeshape.graph.property.ValueFactories;
50  import org.modeshape.graph.sequencer.StreamSequencer;
51  import org.modeshape.graph.sequencer.StreamSequencerContext;
52  import org.modeshape.repository.RepositoryI18n;
53  import org.modeshape.repository.util.RepositoryNodePath;
54  
55  /**
56   * An adapter class that wraps a {@link StreamSequencer} instance to be a {@link Sequencer}.
57   */
58  public class StreamSequencerAdapter implements Sequencer {
59  
60      private static final Logger LOGGER = Logger.getLogger(StreamSequencerAdapter.class);
61  
62      private SequencerConfig configuration;
63      private final StreamSequencer streamSequencer;
64  
65      public StreamSequencerAdapter( StreamSequencer streamSequencer ) {
66          this.streamSequencer = streamSequencer;
67      }
68  
69      /**
70       * {@inheritDoc}
71       */
72      public SequencerConfig getConfiguration() {
73          return this.configuration;
74      }
75  
76      /**
77       * {@inheritDoc}
78       */
79      public void setConfiguration( SequencerConfig configuration ) {
80          this.configuration = configuration;
81  
82          /*
83           * Try to pass the configured properties through to the stream sequencer
84           */
85          if (configuration.getProperties() != null) {
86              final Class<?> streamSequencerClass = streamSequencer.getClass();
87              Reflection reflection = new Reflection(streamSequencerClass);
88              // Try to set the 'classpath' property first ...
89              try {
90                  reflection.invokeSetterMethodOnTarget("classpath", streamSequencer, configuration.getComponentClasspathArray());
91              } catch (Exception e) {
92                  // Ignore, but try the list form ...
93                  try {
94                      reflection.invokeSetterMethodOnTarget("classpath", streamSequencer, configuration.getComponentClasspath());
95                  } catch (Exception e2) {
96                      // Ignore ...
97                  }
98              }
99              // Now set all the other properties ...
100             for (Map.Entry<String, Object> entry : configuration.getProperties().entrySet()) {
101                 // Set the JavaBean-style property on the RepositorySource instance ...
102                 final String propertyName = entry.getKey();
103                 try {
104                     reflection.invokeSetterMethodOnTarget(propertyName, streamSequencer, entry.getValue());
105                     LOGGER.trace("Set '{0}' property from sequencer configuration on '{1}' stream sequencer implementation to {2}",
106                                  propertyName,
107                                  streamSequencerClass.getName(),
108                                  entry.getValue());
109                 } catch (NoSuchMethodException e) {
110                     // If the value is an Object[], see if the values are compatible with String[] and try again
111                 } catch (Exception ignore) {
112                     LOGGER.debug("Unable to set '{0}' property from sequencer configuration on '{1}' stream sequencer implementation",
113                                  propertyName,
114                                  streamSequencerClass.getName());
115                     // It's possible that these properties weren't intended for the stream sequencer anyway
116                 }
117             }
118         }
119     }
120 
121     /**
122      * {@inheritDoc}
123      */
124     public void execute( Node input,
125                          String sequencedPropertyName,
126                          NetChange changes,
127                          Set<RepositoryNodePath> outputPaths,
128                          SequencerContext context,
129                          Problems problems ) throws SequencerException {
130         // 'sequencedPropertyName' contains the name of the modified property on 'input' that resulted in the call to this
131         // sequencer.
132         // 'changes' contains all of the changes to this node that occurred in the transaction.
133         // 'outputPaths' contains the paths of the node(s) where this sequencer is to save it's data.
134 
135         // Get the property that contains the data, given by 'propertyName' ...
136         Property sequencedProperty = input.getProperty(sequencedPropertyName);
137 
138         if (sequencedProperty == null || sequencedProperty.isEmpty()) {
139             String msg = RepositoryI18n.unableToFindPropertyForSequencing.text(sequencedPropertyName, input.getLocation());
140             throw new SequencerException(msg);
141         }
142 
143         // Get the binary property with the image content, and build the image metadata from the image ...
144         ValueFactories factories = context.getExecutionContext().getValueFactories();
145         SequencerOutputMap output = new SequencerOutputMap(factories);
146         InputStream stream = null;
147         Throwable firstError = null;
148         Binary binary = factories.getBinaryFactory().create(sequencedProperty.getFirstValue());
149         binary.acquire();
150         try {
151             // Parallel the JCR lemma for converting objects into streams
152             stream = binary.getStream();
153             StreamSequencerContext StreamSequencerContext = createStreamSequencerContext(input,
154                                                                                          sequencedProperty,
155                                                                                          context,
156                                                                                          problems);
157             this.streamSequencer.sequence(stream, output, StreamSequencerContext);
158         } catch (Throwable t) {
159             // Record the error ...
160             firstError = t;
161         } finally {
162             try {
163                 if (stream != null) {
164                     // Always close the stream, recording the error if we've not yet seen an error
165                     try {
166                         stream.close();
167                     } catch (Throwable t) {
168                         if (firstError == null) firstError = t;
169                     } finally {
170                         stream = null;
171                     }
172                 }
173                 if (firstError != null) {
174                     // Wrap and throw the first error that we saw ...
175                     throw new SequencerException(firstError);
176                 }
177             } finally {
178                 binary.release();
179             }
180         }
181 
182         // Accumulator of paths that we've added to the batch but have not yet been submitted to the graph
183         Set<Path> builtPaths = new HashSet<Path>();
184 
185         // Find each output node and save the image metadata there ...
186         for (RepositoryNodePath outputPath : outputPaths) {
187             // Get the name of the repository workspace and the path to the output node
188             final String repositoryWorkspaceName = outputPath.getWorkspaceName();
189             final String nodePath = outputPath.getNodePath();
190 
191             // Find or create the output node in this session ...
192             context.graph().useWorkspace(repositoryWorkspaceName);
193 
194             buildPathTo(nodePath, context, builtPaths);
195             // Node outputNode = context.graph().getNodeAt(nodePath);
196 
197             // Now save the image metadata to the output node ...
198             saveOutput(nodePath, output, context, builtPaths);
199         }
200 
201         context.getDestination().submit();
202     }
203 
204     /**
205      * Creates all nodes along the given node path if they are missing. Ensures that nodePath is a valid path to a node.
206      * 
207      * @param nodePath the node path to create
208      * @param context the sequencer context under which it should be created
209      * @param builtPaths a set of the paths that have already been created but not submitted in this batch
210      */
211     private void buildPathTo( String nodePath,
212                               SequencerContext context,
213                               Set<Path> builtPaths ) {
214         PathFactory pathFactory = context.getExecutionContext().getValueFactories().getPathFactory();
215         Path targetPath = pathFactory.create(nodePath);
216 
217         buildPathTo(targetPath, context, builtPaths);
218     }
219 
220     /**
221      * Creates all nodes along the given node path if they are missing. Ensures that nodePath is a valid path to a node.
222      * 
223      * @param targetPath the node path to create
224      * @param context the sequencer context under which it should be created
225      * @param builtPaths a set of the paths that have already been created but not submitted in this batch
226      */
227     private void buildPathTo( Path targetPath,
228                               SequencerContext context,
229                               Set<Path> builtPaths ) {
230         PathFactory pathFactory = context.getExecutionContext().getValueFactories().getPathFactory();
231         PropertyFactory propFactory = context.getExecutionContext().getPropertyFactory();
232 
233         if (targetPath.isRoot()) return;
234         Path workingPath = pathFactory.createRootPath();
235         Path.Segment[] segments = targetPath.getSegmentsArray();
236         int i = 0;
237         Property primaryType = propFactory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.UNSTRUCTURED);
238         for (int max = segments.length; i < max; i++) {
239             workingPath = pathFactory.create(workingPath, segments[i]);
240 
241             if (!builtPaths.contains(workingPath)) {
242                 try {
243                     context.graph().getNodeAt(workingPath);
244                 } catch (PathNotFoundException pnfe) {
245                     context.getDestination().create(workingPath, primaryType);
246                     builtPaths.add(workingPath);
247                 }
248             }
249         }
250     }
251 
252     /**
253      * Save the sequencing output to the supplied node. This method does not need to save the output, as that is done by the
254      * caller of this method.
255      * 
256      * @param nodePath the existing node onto (or below) which the output is to be written; never null
257      * @param output the (immutable) sequencing output; never null
258      * @param context the execution context for this sequencing operation; never null
259      * @param builtPaths a set of the paths that have already been created but not submitted in this batch
260      */
261     protected void saveOutput( String nodePath,
262                                SequencerOutputMap output,
263                                SequencerContext context,
264                                Set<Path> builtPaths ) {
265         if (output.isEmpty()) return;
266         final PathFactory pathFactory = context.getExecutionContext().getValueFactories().getPathFactory();
267         final PropertyFactory propertyFactory = context.getExecutionContext().getPropertyFactory();
268         final Path outputNodePath = pathFactory.create(nodePath);
269 
270         // Iterate over the entries in the output, in Path's natural order (shorter paths first and in lexicographical order by
271         // prefix and name)
272         for (SequencerOutputMap.Entry entry : output) {
273             Path targetNodePath = entry.getPath();
274 
275             // Resolve this path relative to the output node path, handling any parent or self references ...
276             Path absolutePath = targetNodePath.isAbsolute() ? targetNodePath : outputNodePath.resolve(targetNodePath);
277 
278             List<Property> properties = new LinkedList<Property>();
279             // Set all of the properties on this
280             for (SequencerOutputMap.PropertyValue property : entry.getPropertyValues()) {
281                 if (property.getValue() instanceof Object[]) {
282                     // Have to force this cast or a single-valued property gets created with a value that is an Object[]
283                     properties.add(propertyFactory.create(property.getName(), (Object[])property.getValue()));
284                 } else if (property.getValue() != null) {
285                     properties.add(propertyFactory.create(property.getName(), property.getValue()));
286                 }
287                 // TODO: Handle reference properties - currently passed in as Paths
288             }
289 
290             if (absolutePath.getParent() != null) {
291                 buildPathTo(absolutePath.getParent(), context, builtPaths);
292             }
293             context.getDestination().create(absolutePath, properties);
294             builtPaths.add(absolutePath);
295         }
296     }
297 
298     protected String[] extractMixinTypes( Object value ) {
299         if (value instanceof String[]) return (String[])value;
300         if (value instanceof String) return new String[] {(String)value};
301         return null;
302     }
303 
304     protected StreamSequencerContext createStreamSequencerContext( Node input,
305                                                                    Property sequencedProperty,
306                                                                    SequencerContext context,
307                                                                    Problems problems ) {
308         assert input != null;
309         assert sequencedProperty != null;
310         assert context != null;
311         assert problems != null;
312         ValueFactories factories = context.getExecutionContext().getValueFactories();
313         Path path = factories.getPathFactory().create(input.getLocation().getPath());
314 
315         Set<org.modeshape.graph.property.Property> props = Collections.<Property>unmodifiableSet(input.getPropertiesByName()
316                                                                                                       .values());
317         Name fileName = path.getLastSegment().getName();
318         if (JcrLexicon.CONTENT.equals(fileName) && !path.isRoot()) {
319             // We're actually sequencing the "jcr:content" child node of an "nt:file" node, but the name of
320             // the file is actually the name of the "jcr:content" node's parent "nt:file" node.
321             fileName = path.getParent().getLastSegment().getName();
322         }
323         String mimeType = getMimeType(context, sequencedProperty, fileName.getLocalName());
324         return new StreamSequencerContext(context.getExecutionContext(), path, props, mimeType, problems);
325     }
326 
327     protected String getMimeType( SequencerContext context,
328                                   Property sequencedProperty,
329                                   String name ) {
330         SequencerException err = null;
331         String mimeType = null;
332         InputStream stream = null;
333         try {
334             // Parallel the JCR lemma for converting objects into streams
335             stream = new ByteArrayInputStream(sequencedProperty.toString().getBytes());
336             mimeType = context.getExecutionContext().getMimeTypeDetector().mimeTypeOf(name, stream);
337             return mimeType;
338         } catch (Exception error) {
339             err = new SequencerException(error);
340         } finally {
341             if (stream != null) {
342                 try {
343                     stream.close();
344                 } catch (IOException error) {
345                     // Only throw exception if an exception was not already thrown
346                     if (err == null) err = new SequencerException(error);
347                 }
348             }
349         }
350         throw err;
351     }
352 }