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 }