001    /*
002     * JBoss, Home of Professional Open Source.
003     * Copyright 2008, Red Hat Middleware LLC, and individual contributors
004     * as indicated by the @author tags. See the copyright.txt file in the
005     * distribution for a full listing of individual contributors. 
006     *
007     * This is free software; you can redistribute it and/or modify it
008     * under the terms of the GNU Lesser General Public License as
009     * published by the Free Software Foundation; either version 2.1 of
010     * the License, or (at your option) any later version.
011     *
012     * This software is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015     * Lesser General Public License for more details.
016     *
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this software; if not, write to the Free
019     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021     */
022    package org.jboss.dna.repository.sequencers;
023    
024    import java.io.InputStream;
025    import java.math.BigDecimal;
026    import java.util.Calendar;
027    import java.util.Date;
028    import java.util.Set;
029    import javax.jcr.Node;
030    import javax.jcr.PathNotFoundException;
031    import javax.jcr.Property;
032    import javax.jcr.RepositoryException;
033    import javax.jcr.Session;
034    import org.jboss.dna.common.monitor.ProgressMonitor;
035    import org.jboss.dna.common.util.Logger;
036    import org.jboss.dna.graph.properties.Binary;
037    import org.jboss.dna.graph.properties.DateTime;
038    import org.jboss.dna.graph.properties.Name;
039    import org.jboss.dna.graph.properties.NamespaceRegistry;
040    import org.jboss.dna.graph.properties.Path;
041    import org.jboss.dna.graph.properties.PathFactory;
042    import org.jboss.dna.graph.sequencers.StreamSequencer;
043    import org.jboss.dna.repository.RepositoryI18n;
044    import org.jboss.dna.repository.observation.NodeChange;
045    import org.jboss.dna.repository.util.JcrExecutionContext;
046    import org.jboss.dna.repository.util.RepositoryNodePath;
047    
048    /**
049     * An adapter class that wraps a {@link StreamSequencer} instance to be a {@link Sequencer}.
050     * 
051     * @author Randall Hauch
052     */
053    public class StreamSequencerAdapter implements Sequencer {
054    
055        private SequencerConfig configuration;
056        private final StreamSequencer streamSequencer;
057    
058        public StreamSequencerAdapter( StreamSequencer streamSequencer ) {
059            this.streamSequencer = streamSequencer;
060        }
061    
062        /**
063         * {@inheritDoc}
064         */
065        public SequencerConfig getConfiguration() {
066            return this.configuration;
067        }
068    
069        /**
070         * {@inheritDoc}
071         */
072        public void setConfiguration( SequencerConfig configuration ) {
073            this.configuration = configuration;
074        }
075    
076        /**
077         * {@inheritDoc}
078         */
079        public void execute( Node input,
080                             String sequencedPropertyName,
081                             NodeChange changes,
082                             Set<RepositoryNodePath> outputPaths,
083                             JcrExecutionContext execContext,
084                             ProgressMonitor progressMonitor ) throws RepositoryException, SequencerException {
085            // 'sequencedPropertyName' contains the name of the modified property on 'input' that resulted in the call to this
086            // sequencer.
087            // 'changes' contains all of the changes to this node that occurred in the transaction.
088            // 'outputPaths' contains the paths of the node(s) where this sequencer is to save it's data.
089    
090            try {
091                progressMonitor.beginTask(100, RepositoryI18n.sequencingPropertyOnNode, sequencedPropertyName, input.getPath());
092    
093                // Get the property that contains the data, given by 'propertyName' ...
094                Property sequencedProperty = null;
095                try {
096                    sequencedProperty = input.getProperty(sequencedPropertyName);
097                } catch (PathNotFoundException e) {
098                    String msg = RepositoryI18n.unableToFindPropertyForSequencing.text(sequencedPropertyName, input.getPath());
099                    throw new SequencerException(msg, e);
100                }
101                progressMonitor.worked(10);
102    
103                // Get the binary property with the image content, and build the image metadata from the image ...
104                SequencerOutputMap output = new SequencerOutputMap(execContext.getValueFactories());
105                InputStream stream = null;
106                Throwable firstError = null;
107                ProgressMonitor sequencingMonitor = progressMonitor.createSubtask(50);
108                try {
109                    stream = sequencedProperty.getStream();
110                    SequencerNodeContext sequencerContext = new SequencerNodeContext(input, sequencedProperty, execContext);
111                    this.streamSequencer.sequence(stream, output, sequencerContext, sequencingMonitor);
112                } catch (Throwable t) {
113                    // Record the error ...
114                    firstError = t;
115                } finally {
116                    sequencingMonitor.done();
117                    if (stream != null) {
118                        // Always close the stream, recording the error if we've not yet seen an error
119                        try {
120                            stream.close();
121                        } catch (Throwable t) {
122                            if (firstError == null) firstError = t;
123                        } finally {
124                            stream = null;
125                        }
126                    }
127                    if (firstError != null) {
128                        // Wrap and throw the first error that we saw ...
129                        throw new SequencerException(firstError);
130                    }
131                }
132    
133                // Find each output node and save the image metadata there ...
134                ProgressMonitor writingProgress = progressMonitor.createSubtask(40);
135                writingProgress.beginTask(outputPaths.size(),
136                                          RepositoryI18n.writingOutputSequencedFromPropertyOnNodes,
137                                          sequencedPropertyName,
138                                          input.getPath(),
139                                          outputPaths.size());
140                for (RepositoryNodePath outputPath : outputPaths) {
141                    Session session = null;
142                    try {
143                        // Get the name of the repository workspace and the path to the output node
144                        final String repositoryWorkspaceName = outputPath.getRepositoryWorkspaceName();
145                        final String nodePath = outputPath.getNodePath();
146    
147                        // Create a session to the repository where the data should be written ...
148                        session = execContext.getSessionFactory().createSession(repositoryWorkspaceName);
149    
150                        // Find or create the output node in this session ...
151                        Node outputNode = execContext.getTools().findOrCreateNode(session, nodePath);
152    
153                        // Now save the image metadata to the output node ...
154                        if (saveOutput(outputNode, output, execContext)) {
155                            session.save();
156                        }
157                    } finally {
158                        writingProgress.worked(1);
159                        // Always close the session ...
160                        if (session != null) session.logout();
161                    }
162                }
163                writingProgress.done();
164            } finally {
165                progressMonitor.done();
166            }
167        }
168    
169        /**
170         * Save the sequencing output to the supplied node. This method does not need to save the output, as that is done by the
171         * caller of this method.
172         * 
173         * @param outputNode the existing node onto (or below) which the output is to be written; never null
174         * @param output the (immutable) sequencing output; never null
175         * @param context the execution context for this sequencing operation; never null
176         * @return true if the output was written to the node, or false if no information was written
177         * @throws RepositoryException
178         */
179        protected boolean saveOutput( Node outputNode,
180                                      SequencerOutputMap output,
181                                      JcrExecutionContext context ) throws RepositoryException {
182            if (output.isEmpty()) return false;
183            final PathFactory pathFactory = context.getValueFactories().getPathFactory();
184            final NamespaceRegistry namespaceRegistry = context.getNamespaceRegistry();
185            final Path outputNodePath = pathFactory.create(outputNode.getPath());
186            final Name jcrPrimaryTypePropertyName = context.getValueFactories().getNameFactory().create("jcr:primaryType");
187    
188            // Iterate over the entries in the output, in Path's natural order (shorter paths first and in lexicographical order by
189            // prefix and name)
190            for (SequencerOutputMap.Entry entry : output) {
191                Path targetNodePath = entry.getPath();
192                Name primaryType = entry.getPrimaryTypeValue();
193    
194                // Resolve this path relative to the output node path, handling any parent or self references ...
195                Path absolutePath = targetNodePath.isAbsolute() ? targetNodePath : outputNodePath.resolve(targetNodePath);
196                Path relativePath = absolutePath.relativeTo(outputNodePath);
197    
198                // Find or add the node (which may involve adding intermediate nodes) ...
199                Node targetNode = outputNode;
200                for (int i = 0, max = relativePath.size(); i != max; ++i) {
201                    Path.Segment segment = relativePath.getSegment(i);
202                    String qualifiedName = segment.getString(namespaceRegistry);
203                    if (targetNode.hasNode(qualifiedName)) {
204                        targetNode = targetNode.getNode(qualifiedName);
205                    } else {
206                        // It doesn't exist, so create it ...
207                        if (segment.hasIndex()) {
208                            // Use a name without an index ...
209                            qualifiedName = segment.getName().getString(namespaceRegistry);
210                        }
211                        // We only have the primary type for the final one ...
212                        if (i == (max - 1) && primaryType != null) {
213                            targetNode = targetNode.addNode(qualifiedName, primaryType.getString(namespaceRegistry,
214                                                                                                 Path.NO_OP_ENCODER));
215                        } else {
216                            targetNode = targetNode.addNode(qualifiedName);
217                        }
218                    }
219                    assert targetNode != null;
220                }
221                assert targetNode != null;
222    
223                // Set all of the properties on this
224                for (SequencerOutputMap.PropertyValue property : entry.getPropertyValues()) {
225                    String propertyName = property.getName().getString(namespaceRegistry, Path.NO_OP_ENCODER);
226                    Object value = property.getValue();
227                    if (jcrPrimaryTypePropertyName.equals(property.getName())) {
228                        // Skip the primary type property (which is protected in Jackrabbit 1.5)
229                        Logger.getLogger(this.getClass()).trace("Skipping property {0}/{1}={2}",
230                                                                targetNode.getPath(),
231                                                                propertyName,
232                                                                value);
233                        continue;
234                    }
235                    Logger.getLogger(this.getClass()).trace("Writing property {0}/{1}={2}", targetNode.getPath(), propertyName, value);
236                    if (value instanceof Boolean) {
237                        targetNode.setProperty(propertyName, ((Boolean)value).booleanValue());
238                    } else if (value instanceof String) {
239                        targetNode.setProperty(propertyName, (String)value);
240                    } else if (value instanceof String[]) {
241                        targetNode.setProperty(propertyName, (String[])value);
242                    } else if (value instanceof Integer) {
243                        targetNode.setProperty(propertyName, ((Integer)value).intValue());
244                    } else if (value instanceof Short) {
245                        targetNode.setProperty(propertyName, ((Short)value).shortValue());
246                    } else if (value instanceof Long) {
247                        targetNode.setProperty(propertyName, ((Long)value).longValue());
248                    } else if (value instanceof Float) {
249                        targetNode.setProperty(propertyName, ((Float)value).floatValue());
250                    } else if (value instanceof Double) {
251                        targetNode.setProperty(propertyName, ((Double)value).doubleValue());
252                    } else if (value instanceof Binary) {
253                        Binary binaryValue = (Binary)value;
254                        try {
255                            binaryValue.acquire();
256                            targetNode.setProperty(propertyName, binaryValue.getStream());
257                        } finally {
258                            binaryValue.release();
259                        }
260                    } else if (value instanceof BigDecimal) {
261                        targetNode.setProperty(propertyName, ((BigDecimal)value).doubleValue());
262                    } else if (value instanceof DateTime) {
263                        targetNode.setProperty(propertyName, ((DateTime)value).toCalendar());
264                    } else if (value instanceof Date) {
265                        DateTime instant = context.getValueFactories().getDateFactory().create((Date)value);
266                        targetNode.setProperty(propertyName, instant.toCalendar());
267                    } else if (value instanceof Calendar) {
268                        targetNode.setProperty(propertyName, (Calendar)value);
269                    } else if (value instanceof Name) {
270                        Name nameValue = (Name)value;
271                        String stringValue = nameValue.getString(namespaceRegistry);
272                        targetNode.setProperty(propertyName, stringValue);
273                    } else if (value instanceof Path) {
274                        // Find the path to reference node ...
275                        Path pathToReferencedNode = (Path)value;
276                        if (!pathToReferencedNode.isAbsolute()) {
277                            // Resolve the path relative to the output node ...
278                            pathToReferencedNode = outputNodePath.resolve(pathToReferencedNode);
279                        }
280                        // Find the referenced node ...
281                        try {
282                            Node referencedNode = outputNode.getNode(pathToReferencedNode.getString());
283                            targetNode.setProperty(propertyName, referencedNode);
284                        } catch (PathNotFoundException e) {
285                            String msg = RepositoryI18n.errorGettingNodeRelativeToNode.text(value, outputNode.getPath());
286                            throw new SequencerException(msg, e);
287                        }
288                    } else if (value == null) {
289                        // Remove the property ...
290                        targetNode.setProperty(propertyName, (String)null);
291                    } else {
292                        String msg = RepositoryI18n.unknownPropertyValueType.text(value, value.getClass().getName());
293                        throw new SequencerException(msg);
294                    }
295                }
296            }
297    
298            return true;
299        }
300    
301        protected String[] extractMixinTypes( Object value ) {
302            if (value instanceof String[]) return (String[])value;
303            if (value instanceof String) return new String[] {(String)value};
304            return null;
305        }
306    
307    }