001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library 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 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.pdftools;
029
030import org.opencms.file.CmsFile;
031import org.opencms.file.CmsObject;
032import org.opencms.loader.CmsImageScaler;
033import org.opencms.main.CmsException;
034import org.opencms.main.CmsLog;
035import org.opencms.main.OpenCms;
036
037import java.io.ByteArrayInputStream;
038import java.net.URI;
039import java.util.regex.Matcher;
040import java.util.regex.Pattern;
041
042import org.apache.commons.logging.Log;
043
044import org.xhtmlrenderer.extend.FSImage;
045import org.xhtmlrenderer.layout.SharedContext;
046import org.xhtmlrenderer.pdf.ITextFSImage;
047import org.xhtmlrenderer.resource.CSSResource;
048import org.xhtmlrenderer.resource.ImageResource;
049import org.xhtmlrenderer.swing.NaiveUserAgent;
050
051import com.lowagie.text.Image;
052
053/**
054 * This class is responsible for loading external resources while generating PDF from XHTML.
055 *
056 * Resources will be loaded from the VFS. Additionally, if there are image scaler parameters in  an image
057 * URI, the scaled image data will be returned. Please note that this class just reads the data from the linked
058 * resources; it will not go through OpenCms's resource loaders, so you can't e.g. use a JSP as a dynamic stylesheet.
059 */
060public class CmsPdfUserAgent extends NaiveUserAgent {
061
062    /** The regex to match image scaler parameters. */
063    public static final Pattern SCALE_PARAMS_PATTERN = Pattern.compile("__scale=(.*?)(?:&|$)");
064
065    /** The image cache capacity. */
066    private static final int IMAGE_CACHE_CAPACITY = 64;
067
068    /** The logger instance for this class. */
069    private static final Log LOG = CmsLog.getLog(CmsPdfUserAgent.class);
070
071    /** The CMS context to use for loading the resources. */
072    private CmsObject m_cms;
073
074    /** The CMS context to use, with the site root set to the root site. */
075    private CmsObject m_rootCms;
076
077    /** The shared context. */
078    private SharedContext m_sharedContext;
079
080    /**
081     * Creates a new instance.<p>
082     *
083     * @param cms the CMS context
084     *
085     * @throws CmsException if something goes wrong
086     */
087    public CmsPdfUserAgent(CmsObject cms)
088    throws CmsException {
089
090        super(IMAGE_CACHE_CAPACITY);
091
092        m_cms = cms;
093        m_rootCms = OpenCms.initCmsObject(cms);
094        m_rootCms.getRequestContext().setSiteRoot("");
095    }
096
097    /**
098     * @see org.xhtmlrenderer.swing.NaiveUserAgent#getBinaryResource(java.lang.String)
099     */
100    @Override
101    public byte[] getBinaryResource(String uri) {
102
103        return readFile(uri);
104    }
105
106    /**
107     * @see org.xhtmlrenderer.swing.NaiveUserAgent#getCSSResource(java.lang.String)
108     */
109    @Override
110    public CSSResource getCSSResource(String uri) {
111
112        return new CSSResource(getStream(readFile(uri)));
113    }
114
115    /**
116     * @see org.xhtmlrenderer.swing.NaiveUserAgent#getImageResource(java.lang.String)
117     */
118    @SuppressWarnings("unchecked")
119    @Override
120    public ImageResource getImageResource(String uri) {
121
122        ImageResource resource = null;
123        resource = (ImageResource)_imageCache.get(uri);
124        if (resource == null) {
125            byte[] imageData = readImage(uri);
126            if (imageData != null) {
127                try {
128                    Image image = Image.getInstance(imageData);
129                    scaleToOutputResolution(image);
130                    resource = new ImageResource(uri, new ITextFSImage(image));
131                    _imageCache.put(uri, resource);
132                } catch (Exception e) {
133                    LOG.error("Problem with getting image resource " + uri, e);
134                }
135            }
136        }
137
138        if (resource != null) {
139            resource = new ImageResource(resource.getImageUri(), (FSImage)((ITextFSImage)resource.getImage()).clone());
140        } else {
141            resource = new ImageResource(uri, null);
142        }
143        return resource;
144    }
145
146    /**
147     * Gets the shared context.<p>
148     *
149     * @return the shared context
150     */
151    public SharedContext getSharedContext() {
152
153        return m_sharedContext;
154    }
155
156    /**
157     * @see org.xhtmlrenderer.swing.NaiveUserAgent#resolveURI(java.lang.String)
158     */
159    @Override
160    public String resolveURI(String uri) {
161
162        // we want to pass the uri unchanged to the get... methods
163        return uri;
164    }
165
166    /**
167     * Sets the shared context.<p>
168     *
169     * @param sharedContext the shared context
170     */
171    public void setSharedContext(SharedContext sharedContext) {
172
173        m_sharedContext = sharedContext;
174    }
175
176    /**
177     * Converts a byte array to an input stream, but returns null if the byte array is null.<p>
178     *
179     * @param data the data
180     * @return the input stream for the data, or null
181     */
182    ByteArrayInputStream getStream(byte[] data) {
183
184        if (data == null) {
185            return null;
186        } else {
187            return new ByteArrayInputStream(data);
188        }
189    }
190
191    /**
192     * Reads a file from the VFS.<p>
193     *
194     * @param uriWithParams the
195     * @return the file data
196     */
197    private byte[] readFile(String uriWithParams) {
198
199        try {
200            String pathAndQuery = OpenCms.getLinkManager().getRootPath(m_cms, uriWithParams);
201            URI uri = new URI(pathAndQuery);
202            String path = uri.getPath();
203            CmsFile file = m_rootCms.readFile(path);
204            return file.getContents();
205        } catch (Exception e) {
206            LOG.error("Problem with reading CSS " + uriWithParams + ": " + e.getLocalizedMessage(), e);
207            return null;
208        }
209    }
210
211    /**
212     * Reads an image from the VFS, scaling it if necessary.<p>
213     *
214     * @param uriWithParams the image uri, possible with scaling parameter
215     *
216     * @return the image data
217     */
218    private byte[] readImage(String uriWithParams) {
219
220        try {
221            String pathAndQuery = OpenCms.getLinkManager().getRootPath(m_cms, uriWithParams);
222            URI uri = new URI(pathAndQuery);
223            String path = uri.getPath();
224            String query = uri.getQuery();
225            String scaleParams = null;
226            if (query != null) {
227                Matcher matcher = SCALE_PARAMS_PATTERN.matcher(query);
228                if (matcher.find()) {
229                    scaleParams = matcher.group(1);
230                }
231            }
232            CmsFile imageFile = m_rootCms.readFile(path);
233            byte[] result = imageFile.getContents();
234            if (scaleParams != null) {
235                CmsImageScaler scaler = new CmsImageScaler(scaleParams);
236                result = scaler.scaleImage(imageFile);
237            }
238            return result;
239        } catch (Exception e) {
240            LOG.error("Problem with reading image " + uriWithParams + ": " + e.getLocalizedMessage(), e);
241            return null;
242        }
243    }
244
245    /**
246     * Scales the image to output resolution.<p>
247     *
248     * @param image the image to scale
249     */
250    private void scaleToOutputResolution(Image image) {
251
252        float factor = m_sharedContext.getDotsPerPixel();
253        if (factor != 1.0f) {
254            image.scaleAbsolute(image.getPlainWidth() * factor, image.getPlainHeight() * factor);
255        }
256    }
257
258}