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