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.collection.Problems;
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     * @author John Verhaeg
053     */
054    public class StreamSequencerAdapter implements Sequencer {
055    
056        private SequencerConfig configuration;
057        private final StreamSequencer streamSequencer;
058    
059        public StreamSequencerAdapter( StreamSequencer streamSequencer ) {
060            this.streamSequencer = streamSequencer;
061        }
062    
063        /**
064         * {@inheritDoc}
065         */
066        public SequencerConfig getConfiguration() {
067            return this.configuration;
068        }
069    
070        /**
071         * {@inheritDoc}
072         */
073        public void setConfiguration( SequencerConfig configuration ) {
074            this.configuration = configuration;
075        }
076    
077        /**
078         * {@inheritDoc}
079         */
080        public void execute( Node input,
081                             String sequencedPropertyName,
082                             NodeChange changes,
083                             Set<RepositoryNodePath> outputPaths,
084                             JcrExecutionContext execContext,
085                             Problems problems ) throws RepositoryException, SequencerException {
086            // 'sequencedPropertyName' contains the name of the modified property on 'input' that resulted in the call to this
087            // sequencer.
088            // 'changes' contains all of the changes to this node that occurred in the transaction.
089            // 'outputPaths' contains the paths of the node(s) where this sequencer is to save it's data.
090    
091            // Get the property that contains the data, given by 'propertyName' ...
092            Property sequencedProperty = null;
093            try {
094                sequencedProperty = input.getProperty(sequencedPropertyName);
095            } catch (PathNotFoundException e) {
096                String msg = RepositoryI18n.unableToFindPropertyForSequencing.text(sequencedPropertyName, input.getPath());
097                throw new SequencerException(msg, e);
098            }
099    
100            // Get the binary property with the image content, and build the image metadata from the image ...
101            SequencerOutputMap output = new SequencerOutputMap(execContext.getValueFactories());
102            InputStream stream = null;
103            Throwable firstError = null;
104            try {
105                stream = sequencedProperty.getStream();
106                SequencerNodeContext sequencerContext = new SequencerNodeContext(input, sequencedProperty, execContext, problems);
107                this.streamSequencer.sequence(stream, output, sequencerContext);
108            } catch (Throwable t) {
109                // Record the error ...
110                firstError = t;
111            } finally {
112                if (stream != null) {
113                    // Always close the stream, recording the error if we've not yet seen an error
114                    try {
115                        stream.close();
116                    } catch (Throwable t) {
117                        if (firstError == null) firstError = t;
118                    } finally {
119                        stream = null;
120                    }
121                }
122                if (firstError != null) {
123                    // Wrap and throw the first error that we saw ...
124                    throw new SequencerException(firstError);
125                }
126            }
127    
128            // Find each output node and save the image metadata there ...
129            for (RepositoryNodePath outputPath : outputPaths) {
130                Session session = null;
131                try {
132                    // Get the name of the repository workspace and the path to the output node
133                    final String repositoryWorkspaceName = outputPath.getRepositoryWorkspaceName();
134                    final String nodePath = outputPath.getNodePath();
135    
136                    // Create a session to the repository where the data should be written ...
137                    session = execContext.getSessionFactory().createSession(repositoryWorkspaceName);
138    
139                    // Find or create the output node in this session ...
140                    Node outputNode = execContext.getTools().findOrCreateNode(session, nodePath);
141    
142                    // Now save the image metadata to the output node ...
143                    if (saveOutput(outputNode, output, execContext)) {
144                        session.save();
145                    }
146                } finally {
147                    // Always close the session ...
148                    if (session != null) session.logout();
149                }
150            }
151        }
152    
153        /**
154         * Save the sequencing output to the supplied node. This method does not need to save the output, as that is done by the
155         * caller of this method.
156         * 
157         * @param outputNode the existing node onto (or below) which the output is to be written; never null
158         * @param output the (immutable) sequencing output; never null
159         * @param context the execution context for this sequencing operation; never null
160         * @return true if the output was written to the node, or false if no information was written
161         * @throws RepositoryException
162         */
163        protected boolean saveOutput( Node outputNode,
164                                      SequencerOutputMap output,
165                                      JcrExecutionContext context ) throws RepositoryException {
166            if (output.isEmpty()) return false;
167            final PathFactory pathFactory = context.getValueFactories().getPathFactory();
168            final NamespaceRegistry namespaceRegistry = context.getNamespaceRegistry();
169            final Path outputNodePath = pathFactory.create(outputNode.getPath());
170            final Name jcrPrimaryTypePropertyName = context.getValueFactories().getNameFactory().create("jcr:primaryType");
171    
172            // Iterate over the entries in the output, in Path's natural order (shorter paths first and in lexicographical order by
173            // prefix and name)
174            for (SequencerOutputMap.Entry entry : output) {
175                Path targetNodePath = entry.getPath();
176                Name primaryType = entry.getPrimaryTypeValue();
177    
178                // Resolve this path relative to the output node path, handling any parent or self references ...
179                Path absolutePath = targetNodePath.isAbsolute() ? targetNodePath : outputNodePath.resolve(targetNodePath);
180                Path relativePath = absolutePath.relativeTo(outputNodePath);
181    
182                // Find or add the node (which may involve adding intermediate nodes) ...
183                Node targetNode = outputNode;
184                for (int i = 0, max = relativePath.size(); i != max; ++i) {
185                    Path.Segment segment = relativePath.getSegment(i);
186                    String qualifiedName = segment.getString(namespaceRegistry);
187                    if (targetNode.hasNode(qualifiedName)) {
188                        targetNode = targetNode.getNode(qualifiedName);
189                    } else {
190                        // It doesn't exist, so create it ...
191                        if (segment.hasIndex()) {
192                            // Use a name without an index ...
193                            qualifiedName = segment.getName().getString(namespaceRegistry);
194                        }
195                        // We only have the primary type for the final one ...
196                        if (i == (max - 1) && primaryType != null) {
197                            targetNode = targetNode.addNode(qualifiedName, primaryType.getString(namespaceRegistry,
198                                                                                                 Path.NO_OP_ENCODER));
199                        } else {
200                            targetNode = targetNode.addNode(qualifiedName);
201                        }
202                    }
203                    assert targetNode != null;
204                }
205                assert targetNode != null;
206    
207                // Set all of the properties on this
208                for (SequencerOutputMap.PropertyValue property : entry.getPropertyValues()) {
209                    String propertyName = property.getName().getString(namespaceRegistry, Path.NO_OP_ENCODER);
210                    Object value = property.getValue();
211                    if (jcrPrimaryTypePropertyName.equals(property.getName())) {
212                        // Skip the primary type property (which is protected in Jackrabbit 1.5)
213                        Logger.getLogger(this.getClass()).trace("Skipping property {0}/{1}={2}",
214                                                                targetNode.getPath(),
215                                                                propertyName,
216                                                                value);
217                        continue;
218                    }
219                    Logger.getLogger(this.getClass()).trace("Writing property {0}/{1}={2}", targetNode.getPath(), propertyName, value);
220                    if (value instanceof Boolean) {
221                        targetNode.setProperty(propertyName, ((Boolean)value).booleanValue());
222                    } else if (value instanceof String) {
223                        targetNode.setProperty(propertyName, (String)value);
224                    } else if (value instanceof String[]) {
225                        targetNode.setProperty(propertyName, (String[])value);
226                    } else if (value instanceof Integer) {
227                        targetNode.setProperty(propertyName, ((Integer)value).intValue());
228                    } else if (value instanceof Short) {
229                        targetNode.setProperty(propertyName, ((Short)value).shortValue());
230                    } else if (value instanceof Long) {
231                        targetNode.setProperty(propertyName, ((Long)value).longValue());
232                    } else if (value instanceof Float) {
233                        targetNode.setProperty(propertyName, ((Float)value).floatValue());
234                    } else if (value instanceof Double) {
235                        targetNode.setProperty(propertyName, ((Double)value).doubleValue());
236                    } else if (value instanceof Binary) {
237                        Binary binaryValue = (Binary)value;
238                        try {
239                            binaryValue.acquire();
240                            targetNode.setProperty(propertyName, binaryValue.getStream());
241                        } finally {
242                            binaryValue.release();
243                        }
244                    } else if (value instanceof BigDecimal) {
245                        targetNode.setProperty(propertyName, ((BigDecimal)value).doubleValue());
246                    } else if (value instanceof DateTime) {
247                        targetNode.setProperty(propertyName, ((DateTime)value).toCalendar());
248                    } else if (value instanceof Date) {
249                        DateTime instant = context.getValueFactories().getDateFactory().create((Date)value);
250                        targetNode.setProperty(propertyName, instant.toCalendar());
251                    } else if (value instanceof Calendar) {
252                        targetNode.setProperty(propertyName, (Calendar)value);
253                    } else if (value instanceof Name) {
254                        Name nameValue = (Name)value;
255                        String stringValue = nameValue.getString(namespaceRegistry);
256                        targetNode.setProperty(propertyName, stringValue);
257                    } else if (value instanceof Path) {
258                        // Find the path to reference node ...
259                        Path pathToReferencedNode = (Path)value;
260                        if (!pathToReferencedNode.isAbsolute()) {
261                            // Resolve the path relative to the output node ...
262                            pathToReferencedNode = outputNodePath.resolve(pathToReferencedNode);
263                        }
264                        // Find the referenced node ...
265                        try {
266                            Node referencedNode = outputNode.getNode(pathToReferencedNode.getString());
267                            targetNode.setProperty(propertyName, referencedNode);
268                        } catch (PathNotFoundException e) {
269                            String msg = RepositoryI18n.errorGettingNodeRelativeToNode.text(value, outputNode.getPath());
270                            throw new SequencerException(msg, e);
271                        }
272                    } else if (value == null) {
273                        // Remove the property ...
274                        targetNode.setProperty(propertyName, (String)null);
275                    } else {
276                        String msg = RepositoryI18n.unknownPropertyValueType.text(value, value.getClass().getName());
277                        throw new SequencerException(msg);
278                    }
279                }
280            }
281    
282            return true;
283        }
284    
285        protected String[] extractMixinTypes( Object value ) {
286            if (value instanceof String[]) return (String[])value;
287            if (value instanceof String) return new String[] {(String)value};
288            return null;
289        }
290    
291    }