1 /************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 23 package com.sun.star.report.pentaho.output; 24 25 import com.sun.star.report.ImageService; 26 import com.sun.star.report.InputRepository; 27 import com.sun.star.report.OutputRepository; 28 import com.sun.star.report.ReportExecutionException; 29 import com.sun.star.report.pentaho.DefaultNameGenerator; 30 31 import java.awt.Dimension; 32 import java.awt.Image; 33 34 import java.io.BufferedInputStream; 35 import java.io.ByteArrayInputStream; 36 import java.io.ByteArrayOutputStream; 37 import java.io.IOException; 38 import java.io.InputStream; 39 import java.io.OutputStream; 40 41 import java.net.MalformedURLException; 42 import java.net.URI; 43 import java.net.URISyntaxException; 44 import java.net.URL; 45 import java.net.URLConnection; 46 47 import java.sql.Blob; 48 import java.sql.SQLException; 49 50 import java.util.Arrays; 51 import java.util.HashMap; 52 import java.util.Map; 53 import java.util.logging.Level; 54 import java.util.logging.Logger; 55 56 import org.apache.commons.logging.Log; 57 import org.apache.commons.logging.LogFactory; 58 59 import org.jfree.layouting.input.style.values.CSSNumericType; 60 import org.jfree.layouting.input.style.values.CSSNumericValue; 61 62 import org.pentaho.reporting.libraries.base.util.IOUtils; 63 import org.pentaho.reporting.libraries.base.util.PngEncoder; 64 import org.pentaho.reporting.libraries.base.util.WaitingImageObserver; 65 66 67 /** 68 * This class manages the images embedded in a report. 69 * 70 * @author Thomas Morgner 71 * @since 31.03.2007 72 */ 73 public class ImageProducer 74 { 75 76 private static final Log LOGGER = LogFactory.getLog(ImageProducer.class); 77 78 public static class OfficeImage 79 { 80 81 private final CSSNumericValue width; 82 private final CSSNumericValue height; 83 private final String embeddableLink; 84 OfficeImage(final String embeddableLink, final CSSNumericValue width, final CSSNumericValue height)85 public OfficeImage(final String embeddableLink, final CSSNumericValue width, final CSSNumericValue height) 86 { 87 this.embeddableLink = embeddableLink; 88 this.width = width; 89 this.height = height; 90 } 91 getWidth()92 public CSSNumericValue getWidth() 93 { 94 return width; 95 } 96 getHeight()97 public CSSNumericValue getHeight() 98 { 99 return height; 100 } 101 getEmbeddableLink()102 public String getEmbeddableLink() 103 { 104 return embeddableLink; 105 } 106 } 107 108 private static class ByteDataImageKey 109 { 110 111 private final byte[] keyData; 112 private Integer hashCode; 113 ByteDataImageKey(final byte[] keyData)114 protected ByteDataImageKey(final byte[] keyData) 115 { 116 if (keyData == null) 117 { 118 throw new NullPointerException(); 119 } 120 this.keyData = keyData; 121 } 122 equals(final Object o)123 public boolean equals(final Object o) 124 { 125 if (this != o) 126 { 127 if (o == null || getClass() != o.getClass()) 128 { 129 return false; 130 } 131 132 final ByteDataImageKey key = (ByteDataImageKey) o; 133 if (!Arrays.equals(keyData, key.keyData)) 134 { 135 return false; 136 } 137 } 138 139 return true; 140 } 141 hashCode()142 public int hashCode() 143 { 144 if (hashCode != null) 145 { 146 return hashCode; 147 } 148 149 final int length = Math.min(keyData.length, 512); 150 int hashValue = 0; 151 for (int i = 0; i < length; i++) 152 { 153 final byte b = keyData[i]; 154 hashValue = b + hashValue * 23; 155 } 156 this.hashCode = hashValue; 157 return hashValue; 158 } 159 } 160 private final Map imageCache; 161 private final InputRepository inputRepository; 162 private final OutputRepository outputRepository; 163 private final ImageService imageService; 164 ImageProducer(final InputRepository inputRepository, final OutputRepository outputRepository, final ImageService imageService)165 public ImageProducer(final InputRepository inputRepository, 166 final OutputRepository outputRepository, 167 final ImageService imageService) 168 { 169 if (inputRepository == null) 170 { 171 throw new NullPointerException(); 172 } 173 if (outputRepository == null) 174 { 175 throw new NullPointerException(); 176 } 177 if (imageService == null) 178 { 179 throw new NullPointerException(); 180 } 181 182 this.inputRepository = inputRepository; 183 this.outputRepository = outputRepository; 184 this.imageService = imageService; 185 this.imageCache = new HashMap(); 186 } 187 188 /** 189 * Image-Data can be one of the following types: String, URL, URI, byte-array, blob. 190 * 191 * @param imageData 192 * @param preserveIRI 193 * @return 194 */ produceImage(final Object imageData, final boolean preserveIRI)195 public OfficeImage produceImage(final Object imageData, 196 final boolean preserveIRI) 197 { 198 199 LOGGER.debug("Want to produce image " + imageData); 200 if (imageData instanceof String) 201 { 202 return produceFromString((String) imageData, preserveIRI); 203 } 204 205 if (imageData instanceof URL) 206 { 207 return produceFromURL((URL) imageData, preserveIRI); 208 } 209 210 if (imageData instanceof Blob) 211 { 212 return produceFromBlob((Blob) imageData); 213 } 214 215 if (imageData instanceof byte[]) 216 { 217 return produceFromByteArray((byte[]) imageData); 218 } 219 220 if (imageData instanceof Image) 221 { 222 return produceFromImage((Image) imageData); 223 } 224 // not usable .. 225 return null; 226 } 227 produceFromImage(final Image image)228 private OfficeImage produceFromImage(final Image image) 229 { 230 // quick caching ... use a weak list ... 231 final WaitingImageObserver obs = new WaitingImageObserver(image); 232 obs.waitImageLoaded(); 233 234 final PngEncoder encoder = new PngEncoder(image, PngEncoder.ENCODE_ALPHA, PngEncoder.FILTER_NONE, 5); 235 final byte[] data = encoder.pngEncode(); 236 return produceFromByteArray(data); 237 } 238 produceFromBlob(final Blob blob)239 private OfficeImage produceFromBlob(final Blob blob) 240 { 241 try 242 { 243 final InputStream inputStream = blob.getBinaryStream(); 244 final int length = (int) blob.length(); 245 246 final ByteArrayOutputStream bout = new ByteArrayOutputStream(length); 247 try 248 { 249 IOUtils.getInstance().copyStreams(inputStream, bout); 250 } finally 251 { 252 inputStream.close(); 253 } 254 return produceFromByteArray(bout.toByteArray()); 255 } 256 catch (IOException e) 257 { 258 LOGGER.warn("Failed to produce image from Blob", e); 259 } 260 catch (SQLException e) 261 { 262 LOGGER.warn("Failed to produce image from Blob", e); 263 } 264 return null; 265 } 266 produceFromByteArray(final byte[] data)267 private OfficeImage produceFromByteArray(final byte[] data) 268 { 269 final ByteDataImageKey imageKey = new ByteDataImageKey(data); 270 final OfficeImage o = (OfficeImage) imageCache.get(imageKey); 271 if (o != null) 272 { 273 return o; 274 } 275 276 try 277 { 278 final String mimeType = imageService.getMimeType(data); 279 final Dimension dims = imageService.getImageSize(data); 280 281 // copy the image into the local output-storage 282 // todo: Implement data-fingerprinting so that we can detect the mime-type 283 final OutputRepository storage = outputRepository.openOutputRepository("Pictures", null); 284 final DefaultNameGenerator nameGenerator = new DefaultNameGenerator(storage); 285 final String name = nameGenerator.generateName("image", mimeType); 286 final OutputStream outputStream = storage.createOutputStream(name, mimeType); 287 final ByteArrayInputStream bin = new ByteArrayInputStream(data); 288 289 try 290 { 291 IOUtils.getInstance().copyStreams(bin, outputStream); 292 } finally 293 { 294 outputStream.close(); 295 storage.closeOutputRepository(); 296 } 297 298 final CSSNumericValue widthVal = CSSNumericValue.createValue(CSSNumericType.MM, dims.getWidth() / 100.0); 299 final CSSNumericValue heightVal = CSSNumericValue.createValue(CSSNumericType.MM, dims.getHeight() / 100.0); 300 final OfficeImage officeImage = new OfficeImage("Pictures/" + name, widthVal, heightVal); 301 imageCache.put(imageKey, officeImage); 302 return officeImage; 303 } 304 catch (IOException e) 305 { 306 LOGGER.warn("Failed to load image from local input-repository", e); 307 } 308 catch (ReportExecutionException e) 309 { 310 LOGGER.warn("Failed to create image from local input-repository", e); 311 } 312 return null; 313 } 314 produceFromString(final String source, final boolean preserveIRI)315 private OfficeImage produceFromString(final String source, 316 final boolean preserveIRI) 317 { 318 319 try 320 { 321 final URL url = new URL(source); 322 return produceFromURL(url, preserveIRI); 323 } 324 catch (MalformedURLException e) 325 { 326 // ignore .. but we had to try this .. 327 } 328 329 final OfficeImage o = (OfficeImage) imageCache.get(source); 330 if (o != null) 331 { 332 return o; 333 } 334 335 // Next, check whether this is a local path. 336 if (inputRepository.isReadable(source)) 337 { 338 // cool, the file exists. Let's try to read it. 339 try 340 { 341 final ByteArrayOutputStream bout = new ByteArrayOutputStream(8192); 342 final InputStream inputStream = inputRepository.createInputStream(source); 343 try 344 { 345 IOUtils.getInstance().copyStreams(inputStream, bout); 346 } finally 347 { 348 inputStream.close(); 349 } 350 final byte[] data = bout.toByteArray(); 351 final Dimension dims = imageService.getImageSize(data); 352 final String mimeType = imageService.getMimeType(data); 353 354 final CSSNumericValue widthVal = CSSNumericValue.createValue(CSSNumericType.MM, dims.getWidth() / 100.0); 355 final CSSNumericValue heightVal = CSSNumericValue.createValue(CSSNumericType.MM, dims.getHeight() / 100.0); 356 357 final String filename = copyToOutputRepository(mimeType, data); 358 final OfficeImage officeImage = new OfficeImage(filename, widthVal, heightVal); 359 imageCache.put(source, officeImage); 360 return officeImage; 361 } 362 catch (IOException e) 363 { 364 LOGGER.warn("Failed to load image from local input-repository", e); 365 } 366 catch (ReportExecutionException e) 367 { 368 LOGGER.warn("Failed to create image from local input-repository", e); 369 } 370 } 371 else 372 { 373 try 374 { 375 URI rootURI = new URI(inputRepository.getRootURL()); 376 final URI uri = rootURI.resolve(source); 377 return produceFromURL(uri.toURL(), preserveIRI); 378 } 379 catch (URISyntaxException ex) 380 { 381 } 382 catch (MalformedURLException e) 383 { 384 // ignore .. but we had to try this .. 385 } 386 } 387 388 // Return the image as broken image instead .. 389 final OfficeImage officeImage = new OfficeImage(source, null, null); 390 imageCache.put(source, officeImage); 391 return officeImage; 392 } 393 produceFromURL(final URL url, final boolean preserveIRI)394 private OfficeImage produceFromURL(final URL url, 395 final boolean preserveIRI) 396 { 397 final String urlString = url.toString(); 398 URI uri = null; 399 try 400 { 401 uri = new URI(urlString); 402 } 403 catch (URISyntaxException ex) 404 { 405 Logger.getLogger(ImageProducer.class.getName()).log(Level.SEVERE, null, ex); 406 } 407 final OfficeImage o = (OfficeImage) imageCache.get(uri); 408 if (o != null) 409 { 410 return o; 411 } 412 413 try 414 { 415 final ByteArrayOutputStream bout = new ByteArrayOutputStream(8192); 416 final URLConnection urlConnection = url.openConnection(); 417 final InputStream inputStream = new BufferedInputStream(urlConnection.getInputStream()); 418 try 419 { 420 IOUtils.getInstance().copyStreams(inputStream, bout); 421 } finally 422 { 423 inputStream.close(); 424 } 425 final byte[] data = bout.toByteArray(); 426 427 final Dimension dims = imageService.getImageSize(data); 428 final String mimeType = imageService.getMimeType(data); 429 final CSSNumericValue widthVal = CSSNumericValue.createValue(CSSNumericType.MM, dims.getWidth() / 100.0); 430 final CSSNumericValue heightVal = CSSNumericValue.createValue(CSSNumericType.MM, dims.getHeight() / 100.0); 431 432 if (preserveIRI) 433 { 434 final OfficeImage retval = new OfficeImage(urlString, widthVal, heightVal); 435 imageCache.put(uri, retval); 436 return retval; 437 } 438 439 final String name = copyToOutputRepository(mimeType, data); 440 final OfficeImage officeImage = new OfficeImage(name, widthVal, heightVal); 441 imageCache.put(uri, officeImage); 442 return officeImage; 443 } 444 catch (IOException e) 445 { 446 LOGGER.warn("Failed to load image from local input-repository" + e); 447 } 448 catch (ReportExecutionException e) 449 { 450 LOGGER.warn("Failed to create image from local input-repository" + e); 451 } 452 453 if (!preserveIRI) 454 { 455 final OfficeImage image = new OfficeImage(urlString, null, null); 456 imageCache.put(uri, image); 457 return image; 458 } 459 460 // OK, everything failed; the image is not - repeat it - not usable. 461 return null; 462 } 463 copyToOutputRepository(final String urlMimeType, final byte[] data)464 private String copyToOutputRepository(final String urlMimeType, final byte[] data) 465 throws IOException, ReportExecutionException 466 { 467 final String mimeType; 468 if (urlMimeType == null) 469 { 470 mimeType = imageService.getMimeType(data); 471 } 472 else 473 { 474 mimeType = urlMimeType; 475 } 476 477 // copy the image into the local output-storage 478 final OutputRepository storage = outputRepository.openOutputRepository("Pictures", null); 479 final DefaultNameGenerator nameGenerator = new DefaultNameGenerator(storage); 480 final String name = nameGenerator.generateName("image", mimeType); 481 final OutputStream outputStream = storage.createOutputStream(name, mimeType); 482 final ByteArrayInputStream bin = new ByteArrayInputStream(data); 483 484 try 485 { 486 IOUtils.getInstance().copyStreams(bin, outputStream); 487 } finally 488 { 489 outputStream.close(); 490 storage.closeOutputRepository(); 491 } 492 return "Pictures/" + name; 493 } 494 } 495