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    }