cid#1607171 Data race condition
[LibreOffice.git] / reportbuilder / java / org / libreoffice / report / pentaho / output / ImageProducer.java
blob69995d7aa1c5c5d899da4f61baa9a83528cbda10
1 /*
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;
37 import java.net.URI;
38 import java.net.URISyntaxException;
39 import java.net.URL;
40 import java.net.URLConnection;
42 import java.sql.Blob;
43 import java.sql.SQLException;
45 import java.util.Arrays;
46 import java.util.HashMap;
47 import java.util.Map;
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;
59 /**
60 * This class manages the images embedded in a report.
62 * @since 31.03.2007
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;
79 this.width = width;
80 this.height = height;
83 public CSSNumericValue getWidth()
85 return width;
88 public CSSNumericValue getHeight()
90 return height;
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)
107 if (keyData == null)
109 throw new NullPointerException();
111 this.keyData = keyData;
114 @Override
115 public boolean equals(final Object o)
117 if (this != o)
119 if (o == null || getClass() != o.getClass())
121 return false;
124 final ByteDataImageKey key = (ByteDataImageKey) o;
125 if (!Arrays.equals(keyData, key.keyData))
127 return false;
131 return true;
134 @Override
135 public int hashCode()
137 if (hashCode != null)
139 return hashCode;
142 final int length = Math.min(keyData.length, 512);
143 int hashValue = 0;
144 for (int i = 0; i < length; i++)
146 final byte b = keyData[i];
147 hashValue = b + hashValue * 23;
149 this.hashCode = hashValue;
150 return 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);
213 // not usable ..
214 return null;
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);
239 } finally
241 inputStream.close();
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);
253 return null;
256 private OfficeImage produceFromByteArray(final byte[] data)
258 final ByteDataImageKey imageKey = new ByteDataImageKey(data);
259 final OfficeImage o = imageCache.get(imageKey);
260 if (o != null)
262 return o;
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);
281 } finally
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);
291 return 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);
301 return null;
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);
319 if (o != null)
321 return o;
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);
335 } finally
337 inputStream.close();
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);
349 return 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);
360 else
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);
380 return officeImage;
383 private OfficeImage produceFromURL(final URL url,
384 final boolean preserveIRI)
386 final String urlString = url.toString();
387 URI uri = null;
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);
397 if (o != null)
399 return o;
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);
410 } finally
412 inputStream.close();
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);
421 if (preserveIRI)
423 final OfficeImage retval = new OfficeImage(urlString, widthVal, heightVal);
424 imageCache.put(uri, retval);
425 return retval;
428 final String name = copyToOutputRepository(mimeType, data);
429 final OfficeImage officeImage = new OfficeImage(name, widthVal, heightVal);
430 imageCache.put(uri, officeImage);
431 return 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);
442 if (!preserveIRI)
444 final OfficeImage image = new OfficeImage(urlString, null, null);
445 imageCache.put(uri, image);
446 return image;
449 // OK, everything failed; the image is not - repeat it - not usable.
450 return null;
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);
461 else
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);
476 } finally
478 outputStream.close();
479 storage.closeOutputRepository();
481 return "Pictures/" + name;