2 * This file is part of the LibreOffice project.
4 * This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 * This file incorporates work covered by the following license notice:
10 * Licensed to the Apache Software Foundation (ASF) under one or more
11 * contributor license agreements. See the NOTICE file distributed
12 * with this work for additional information regarding copyright
13 * ownership. The ASF licenses this file to you under the Apache
14 * License, Version 2.0 (the "License"); you may not use this file
15 * except in compliance with the License. You may obtain a copy of
16 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 package org
.libreoffice
.report
.pentaho
.output
;
20 import com
.sun
.star
.awt
.Size
;
21 import org
.libreoffice
.report
.ImageService
;
22 import org
.libreoffice
.report
.InputRepository
;
23 import org
.libreoffice
.report
.OutputRepository
;
24 import org
.libreoffice
.report
.ReportExecutionException
;
25 import org
.libreoffice
.report
.pentaho
.DefaultNameGenerator
;
27 import java
.awt
.Image
;
29 import java
.io
.BufferedInputStream
;
30 import java
.io
.ByteArrayInputStream
;
31 import java
.io
.ByteArrayOutputStream
;
32 import java
.io
.IOException
;
33 import java
.io
.InputStream
;
34 import java
.io
.OutputStream
;
36 import java
.net
.MalformedURLException
;
38 import java
.net
.URISyntaxException
;
40 import java
.net
.URLConnection
;
43 import java
.sql
.SQLException
;
45 import java
.util
.Arrays
;
46 import java
.util
.HashMap
;
48 import java
.util
.logging
.Level
;
49 import java
.util
.logging
.Logger
;
51 import org
.jfree
.layouting
.input
.style
.values
.CSSNumericType
;
52 import org
.jfree
.layouting
.input
.style
.values
.CSSNumericValue
;
54 import org
.pentaho
.reporting
.libraries
.base
.util
.IOUtils
;
55 import org
.pentaho
.reporting
.libraries
.base
.util
.PngEncoder
;
56 import org
.pentaho
.reporting
.libraries
.base
.util
.WaitingImageObserver
;
60 * This class manages the images embedded in a report.
64 public class ImageProducer
67 private static final Logger LOGGER
= Logger
.getLogger(ImageProducer
.class.getName());
69 public static class OfficeImage
72 private final CSSNumericValue width
;
73 private final CSSNumericValue height
;
74 private final String embeddableLink
;
76 private OfficeImage(final String embeddableLink
, final CSSNumericValue width
, final CSSNumericValue height
)
78 this.embeddableLink
= embeddableLink
;
83 public CSSNumericValue
getWidth()
88 public CSSNumericValue
getHeight()
93 public String
getEmbeddableLink()
95 return embeddableLink
;
99 private static class ByteDataImageKey
102 private final byte[] keyData
;
103 private Integer hashCode
;
105 protected ByteDataImageKey(final byte[] keyData
)
109 throw new NullPointerException();
111 this.keyData
= keyData
;
115 public boolean equals(final Object o
)
119 if (o
== null || getClass() != o
.getClass())
124 final ByteDataImageKey key
= (ByteDataImageKey
) o
;
125 if (!Arrays
.equals(keyData
, key
.keyData
))
135 public int hashCode()
137 if (hashCode
!= null)
142 final int length
= Math
.min(keyData
.length
, 512);
144 for (int i
= 0; i
< length
; i
++)
146 final byte b
= keyData
[i
];
147 hashValue
= b
+ hashValue
* 23;
149 this.hashCode
= hashValue
;
153 private final Map
<Object
,OfficeImage
> imageCache
;
154 private final InputRepository inputRepository
;
155 private final OutputRepository outputRepository
;
156 private final ImageService imageService
;
158 public ImageProducer(final InputRepository inputRepository
,
159 final OutputRepository outputRepository
,
160 final ImageService imageService
)
162 if (inputRepository
== null)
164 throw new NullPointerException();
166 if (outputRepository
== null)
168 throw new NullPointerException();
170 if (imageService
== null)
172 throw new NullPointerException();
175 this.inputRepository
= inputRepository
;
176 this.outputRepository
= outputRepository
;
177 this.imageService
= imageService
;
178 this.imageCache
= new HashMap
<Object
,OfficeImage
>();
182 * Image-Data can be one of the following types: String, URL, URI, byte-array, blob.
184 public OfficeImage
produceImage(final Object imageData
,
185 final boolean preserveIRI
)
188 LOGGER
.config("Want to produce image " + imageData
);
189 if (imageData
instanceof String
)
191 return produceFromString((String
) imageData
, preserveIRI
);
194 if (imageData
instanceof URL
)
196 return produceFromURL((URL
) imageData
, preserveIRI
);
199 if (imageData
instanceof Blob
)
201 return produceFromBlob((Blob
) imageData
);
204 if (imageData
instanceof byte[])
206 return produceFromByteArray((byte[]) imageData
);
209 if (imageData
instanceof Image
)
211 return produceFromImage((Image
) imageData
);
217 private OfficeImage
produceFromImage(final Image image
)
219 // quick caching ... use a weak list ...
220 final WaitingImageObserver obs
= new WaitingImageObserver(image
);
221 obs
.waitImageLoaded();
223 final PngEncoder encoder
= new PngEncoder(image
, PngEncoder
.ENCODE_ALPHA
, PngEncoder
.FILTER_NONE
, 5);
224 final byte[] data
= encoder
.pngEncode();
225 return produceFromByteArray(data
);
228 private OfficeImage
produceFromBlob(final Blob blob
)
232 final InputStream inputStream
= blob
.getBinaryStream();
233 final int length
= (int) blob
.length();
235 final ByteArrayOutputStream bout
= new ByteArrayOutputStream(length
);
238 IOUtils
.getInstance().copyStreams(inputStream
, bout
);
243 return produceFromByteArray(bout
.toByteArray());
245 catch (IOException e
)
247 LOGGER
.warning("Failed to produce image from Blob: " + e
);
249 catch (SQLException e
)
251 LOGGER
.warning("Failed to produce image from Blob: " + e
);
256 private OfficeImage
produceFromByteArray(final byte[] data
)
258 final ByteDataImageKey imageKey
= new ByteDataImageKey(data
);
259 final OfficeImage o
= imageCache
.get(imageKey
);
267 final String mimeType
= imageService
.getMimeType(data
);
268 final Size dims
= imageService
.getImageSize(data
);
270 // copy the image into the local output-storage
271 // todo: Implement data-fingerprinting so that we can detect the mime-type
272 final OutputRepository storage
= outputRepository
.openOutputRepository("Pictures", null);
273 final DefaultNameGenerator nameGenerator
= new DefaultNameGenerator(storage
);
274 final String name
= nameGenerator
.generateName("image", mimeType
);
275 final OutputStream outputStream
= storage
.createOutputStream(name
, mimeType
);
276 final ByteArrayInputStream bin
= new ByteArrayInputStream(data
);
280 IOUtils
.getInstance().copyStreams(bin
, outputStream
);
283 outputStream
.close();
284 storage
.closeOutputRepository();
287 final CSSNumericValue widthVal
= CSSNumericValue
.createValue(CSSNumericType
.MM
, dims
.Width
/ 100.0);
288 final CSSNumericValue heightVal
= CSSNumericValue
.createValue(CSSNumericType
.MM
, dims
.Height
/ 100.0);
289 final OfficeImage officeImage
= new OfficeImage("Pictures/" + name
, widthVal
, heightVal
);
290 imageCache
.put(imageKey
, officeImage
);
293 catch (IOException e
)
295 LOGGER
.warning("Failed to load image from local input-repository: " + e
);
297 catch (ReportExecutionException e
)
299 LOGGER
.warning("Failed to create image from local input-repository: " + e
);
304 private OfficeImage
produceFromString(final String source
,
305 final boolean preserveIRI
)
310 final URL url
= new URL(source
);
311 return produceFromURL(url
, preserveIRI
);
313 catch (MalformedURLException e
)
315 // ignore .. but we had to try this ..
318 final OfficeImage o
= imageCache
.get(source
);
324 // Next, check whether this is a local path.
325 if (inputRepository
.isReadable(source
))
327 // cool, the file exists. Let's try to read it.
330 final ByteArrayOutputStream bout
= new ByteArrayOutputStream(8192);
331 final InputStream inputStream
= inputRepository
.createInputStream(source
);
334 IOUtils
.getInstance().copyStreams(inputStream
, bout
);
339 final byte[] data
= bout
.toByteArray();
340 final Size dims
= imageService
.getImageSize(data
);
341 final String mimeType
= imageService
.getMimeType(data
);
343 final CSSNumericValue widthVal
= CSSNumericValue
.createValue(CSSNumericType
.MM
, dims
.Width
/ 100.0);
344 final CSSNumericValue heightVal
= CSSNumericValue
.createValue(CSSNumericType
.MM
, dims
.Height
/ 100.0);
346 final String filename
= copyToOutputRepository(mimeType
, data
);
347 final OfficeImage officeImage
= new OfficeImage(filename
, widthVal
, heightVal
);
348 imageCache
.put(source
, officeImage
);
351 catch (IOException e
)
353 LOGGER
.warning("Failed to load image from local input-repository: " + e
);
355 catch (ReportExecutionException e
)
357 LOGGER
.warning("Failed to create image from local input-repository: " + e
);
364 URI rootURI
= new URI(inputRepository
.getRootURL());
365 final URI uri
= rootURI
.resolve(source
);
366 return produceFromURL(uri
.toURL(), preserveIRI
);
368 catch (URISyntaxException ex
)
371 catch (MalformedURLException e
)
373 // ignore .. but we had to try this ..
377 // Return the image as broken image instead ..
378 final OfficeImage officeImage
= new OfficeImage(source
, null, null);
379 imageCache
.put(source
, officeImage
);
383 private OfficeImage
produceFromURL(final URL url
,
384 final boolean preserveIRI
)
386 final String urlString
= url
.toString();
390 uri
= new URI(urlString
);
392 catch (URISyntaxException ex
)
394 Logger
.getLogger(ImageProducer
.class.getName()).log(Level
.SEVERE
, null, ex
);
396 final OfficeImage o
= imageCache
.get(uri
);
404 final ByteArrayOutputStream bout
= new ByteArrayOutputStream(8192);
405 final URLConnection urlConnection
= url
.openConnection();
406 final InputStream inputStream
= new BufferedInputStream(urlConnection
.getInputStream());
409 IOUtils
.getInstance().copyStreams(inputStream
, bout
);
414 final byte[] data
= bout
.toByteArray();
416 final Size dims
= imageService
.getImageSize(data
);
417 final String mimeType
= imageService
.getMimeType(data
);
418 final CSSNumericValue widthVal
= CSSNumericValue
.createValue(CSSNumericType
.MM
, dims
.Width
/ 100.0);
419 final CSSNumericValue heightVal
= CSSNumericValue
.createValue(CSSNumericType
.MM
, dims
.Height
/ 100.0);
423 final OfficeImage retval
= new OfficeImage(urlString
, widthVal
, heightVal
);
424 imageCache
.put(uri
, retval
);
428 final String name
= copyToOutputRepository(mimeType
, data
);
429 final OfficeImage officeImage
= new OfficeImage(name
, widthVal
, heightVal
);
430 imageCache
.put(uri
, officeImage
);
433 catch (IOException e
)
435 LOGGER
.warning("Failed to load image from local input-repository: " + e
);
437 catch (ReportExecutionException e
)
439 LOGGER
.warning("Failed to create image from local input-repository: " + e
);
444 final OfficeImage image
= new OfficeImage(urlString
, null, null);
445 imageCache
.put(uri
, image
);
449 // OK, everything failed; the image is not - repeat it - not usable.
453 private String
copyToOutputRepository(final String urlMimeType
, final byte[] data
)
454 throws IOException
, ReportExecutionException
456 final String mimeType
;
457 if (urlMimeType
== null)
459 mimeType
= imageService
.getMimeType(data
);
463 mimeType
= urlMimeType
;
466 // copy the image into the local output-storage
467 final OutputRepository storage
= outputRepository
.openOutputRepository("Pictures", null);
468 final DefaultNameGenerator nameGenerator
= new DefaultNameGenerator(storage
);
469 final String name
= nameGenerator
.generateName("image", mimeType
);
470 final OutputStream outputStream
= storage
.createOutputStream(name
, mimeType
);
471 final ByteArrayInputStream bin
= new ByteArrayInputStream(data
);
475 IOUtils
.getInstance().copyStreams(bin
, outputStream
);
478 outputStream
.close();
479 storage
.closeOutputRepository();
481 return "Pictures/" + name
;