001/* 002 * Copyright 2012 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.core.cocoon; 017 018import java.io.File; 019import java.io.FileInputStream; 020import java.io.IOException; 021import java.io.InputStream; 022import java.io.OutputStream; 023import java.io.Serializable; 024import java.net.URI; 025import java.util.HashMap; 026import java.util.Map; 027 028import org.apache.avalon.framework.activity.Disposable; 029import org.apache.avalon.framework.configuration.Configurable; 030import org.apache.avalon.framework.configuration.Configuration; 031import org.apache.avalon.framework.configuration.ConfigurationException; 032import org.apache.avalon.framework.configuration.SAXConfigurationHandler; 033import org.apache.avalon.framework.context.ContextException; 034import org.apache.avalon.framework.context.Contextualizable; 035import org.apache.avalon.framework.service.ServiceException; 036import org.apache.avalon.framework.service.ServiceManager; 037import org.apache.avalon.framework.service.Serviceable; 038import org.apache.cocoon.Constants; 039import org.apache.cocoon.caching.CacheableProcessingComponent; 040import org.apache.cocoon.components.source.SourceUtil; 041import org.apache.cocoon.environment.Context; 042import org.apache.cocoon.serialization.AbstractSerializer; 043import org.apache.excalibur.source.Source; 044import org.apache.excalibur.source.SourceResolver; 045import org.apache.excalibur.source.SourceValidity; 046import org.apache.excalibur.source.impl.validity.NOPValidity; 047import org.apache.fop.apps.FOPException; 048import org.apache.fop.apps.FOUserAgent; 049import org.apache.fop.apps.Fop; 050import org.apache.fop.apps.FopFactory; 051import org.apache.fop.apps.FopFactoryBuilder; 052import org.apache.xmlgraphics.io.Resource; 053import org.apache.xmlgraphics.io.ResourceResolver; 054 055/** 056 * FOP 0.95 (and newer) based serializer. 057 */ 058public class FOPNGSerializer extends AbstractSerializer implements Configurable, CacheableProcessingComponent, Serviceable, ResourceResolver, Disposable, Contextualizable 059{ 060 /** The source resolver */ 061 protected SourceResolver _resolver; 062 063 /** 064 * Factory to create fop objects 065 */ 066 protected FopFactory _fopfactory; 067 068 /** 069 * The FOP instance. 070 */ 071 protected Fop _fop; 072 073 /** 074 * The current <code>mime-type</code>. 075 */ 076 protected String _mimetype; 077 078 /** 079 * Should we set the content length ? 080 */ 081 protected boolean _setContentLength = true; 082 083 /** 084 * Manager to get URLFactory from. 085 */ 086 protected ServiceManager _manager; 087 088 private Map<String, String> _rendererOptions; 089 090 private Context _context; 091 092 public void contextualize(org.apache.avalon.framework.context.Context context) throws ContextException 093 { 094 _context = (Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT); 095 } 096 097 /** 098 * Set the component manager for this serializer. 099 */ 100 public void service(ServiceManager smanager) throws ServiceException 101 { 102 this._manager = smanager; 103 this._resolver = (SourceResolver) this._manager.lookup(SourceResolver.ROLE); 104 } 105 106 /** 107 * Set the configurations for this serializer. 108 */ 109 public void configure(Configuration conf) throws ConfigurationException 110 { 111 // should the content length be set 112 this._setContentLength = conf.getChild("set-content-length").getValueAsBoolean(true); 113 114 Configuration config = null; 115 String configUrl = conf.getChild("user-config").getValue(null); 116 if (configUrl != null) 117 { 118 Source configSource = null; 119 SourceResolver resolver = null; 120 try 121 { 122 resolver = (SourceResolver) this._manager.lookup(SourceResolver.ROLE); 123 configSource = resolver.resolveURI(configUrl); 124 if (getLogger().isDebugEnabled()) 125 { 126 getLogger().debug("Loading configuration from " + configSource.getURI()); 127 } 128 SAXConfigurationHandler configHandler = new SAXConfigurationHandler(); 129 SourceUtil.toSAX(configSource, configHandler); 130 131 config = configHandler.getConfiguration(); 132 } 133 catch (Exception e) 134 { 135 getLogger().warn("Cannot load configuration from " + configUrl); 136 throw new ConfigurationException("Cannot load configuration from " + configUrl, e); 137 } 138 finally 139 { 140 if (resolver != null) 141 { 142 resolver.release(configSource); 143 _manager.release(resolver); 144 } 145 } 146 } 147 148 File base = new File(_context.getRealPath("/")); 149 FopFactoryBuilder fopFactoryBuilder = new FopFactoryBuilder(base.toURI(), this); 150 151 if (config != null) 152 { 153 fopFactoryBuilder.setConfiguration(config); 154 } 155 156 _fopfactory = fopFactoryBuilder.build(); 157 158 // Get the mime type. 159 this._mimetype = conf.getAttribute("mime-type"); 160 161 Configuration confRenderer = conf.getChild("renderer-config"); 162 if (confRenderer != null) 163 { 164 Configuration[] parameters = confRenderer.getChildren("parameter"); 165 if (parameters.length > 0) 166 { 167 _rendererOptions = new HashMap<>(); 168 for (int i = 0; i < parameters.length; i++) 169 { 170 String name = parameters[i].getAttribute("name"); 171 String value = parameters[i].getAttribute("value"); 172 _rendererOptions.put(name, value); 173 if (getLogger().isDebugEnabled()) 174 { 175 getLogger().debug("renderer " + String.valueOf(name) + " = " + String.valueOf(value)); 176 } 177 } 178 } 179 } 180 } 181 182 /** 183 * Recycle serializer by removing references 184 */ 185 @Override 186 public void recycle() 187 { 188 super.recycle(); 189 this._fop = null; 190 } 191 192 public void dispose() 193 { 194 if (this._resolver != null) 195 { 196 this._manager.release(this._resolver); 197 this._resolver = null; 198 } 199 this._manager = null; 200 } 201 202 // ----------------------------------------------------------------- 203 204 /** 205 * Return the MIME type. 206 */ 207 @Override 208 public String getMimeType() 209 { 210 return _mimetype; 211 } 212 213 @Override 214 public void setOutputStream(OutputStream out) throws IOException 215 { 216 217 // Give the source resolver to Batik which is used by FOP 218 // SourceProtocolHandler.setup(this.resolver); 219 220 FOUserAgent userAgent = _fopfactory.newFOUserAgent(); 221 if (this._rendererOptions != null) 222 { 223 userAgent.getRendererOptions().putAll(this._rendererOptions); 224 } 225 userAgent.setAccessibility(true); 226 try 227 { 228 this._fop = _fopfactory.newFop(getMimeType(), userAgent, out); 229 setContentHandler(this._fop.getDefaultHandler()); 230 } 231 catch (FOPException e) 232 { 233 getLogger().error("FOP setup failed", e); 234 throw new IOException("Unable to setup fop: " + e.getLocalizedMessage()); 235 } 236 } 237 238 /** 239 * Generate the unique key. This key must be unique inside the space of this 240 * component. This method must be invoked before the generateValidity() 241 * method. 242 * 243 * @return The generated key or <code>0</code> if the component is currently 244 * not cacheable. 245 */ 246 public Serializable getKey() 247 { 248 return "1"; 249 } 250 251 /** 252 * Generate the validity object. Before this method can be invoked the 253 * generateKey() method must be invoked. 254 * 255 * @return The generated validity object or <code>null</code> if the 256 * component is currently not cacheable. 257 */ 258 public SourceValidity getValidity() 259 { 260 return NOPValidity.SHARED_INSTANCE; 261 } 262 263 /** 264 * Test if the component wants to set the content length 265 */ 266 @Override 267 public boolean shouldSetContentLength() 268 { 269 return this._setContentLength; 270 } 271 272 public Resource getResource(URI uri) throws IOException 273 { 274 String href = uri.toString(); 275 276 if (href.indexOf(':') != -1) 277 { 278 Source source = _resolver.resolveURI(href); 279 return new Resource(source.getMimeType(), source.getInputStream()); 280 } 281 else 282 { 283 String base = _context.getRealPath("/"); 284 285 if (href.startsWith("/")) 286 { 287 href = href.substring(1); 288 } 289 290 File source = new File(base, href); 291 return new Resource(new FileInputStream(source)); 292 } 293 } 294 295 public OutputStream getOutputStream(URI uri) throws IOException 296 { 297 throw new UnsupportedOperationException("getOutputStream"); 298 } 299 300 /** 301 * An InputStream which releases the Cocoon/Avalon source from which the 302 * InputStream has been retrieved when the stream is closed. 303 */ 304 public static final class ReleaseSourceInputStream extends InputStream 305 { 306 private InputStream _delegate; 307 308 private Source _source; 309 310 private SourceResolver _sourceResolver; 311 312 ReleaseSourceInputStream(InputStream delegate, Source source, SourceResolver sourceResolver) 313 { 314 this._delegate = delegate; 315 this._source = source; 316 this._sourceResolver = sourceResolver; 317 } 318 319 @Override 320 public void close() throws IOException 321 { 322 _delegate.close(); 323 _sourceResolver.release(_source); 324 } 325 326 @Override 327 public int read() throws IOException 328 { 329 return _delegate.read(); 330 } 331 332 @Override 333 public int read(byte[] b) throws IOException 334 { 335 return _delegate.read(b); 336 } 337 338 @Override 339 public int read(byte[] b, int off, int len) throws IOException 340 { 341 return _delegate.read(b, off, len); 342 } 343 344 @Override 345 public long skip(long n) throws IOException 346 { 347 return _delegate.skip(n); 348 } 349 350 @Override 351 public int available() throws IOException 352 { 353 return _delegate.available(); 354 } 355 356 @Override 357 public synchronized void mark(int readlimit) 358 { 359 _delegate.mark(readlimit); 360 } 361 362 @Override 363 public synchronized void reset() throws IOException 364 { 365 _delegate.reset(); 366 } 367 368 @Override 369 public boolean markSupported() 370 { 371 return _delegate.markSupported(); 372 } 373 } 374}