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 }