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 }