001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.request.resource;
018
019import java.awt.image.BufferedImage;
020import java.io.ByteArrayOutputStream;
021import java.io.IOException;
022import java.time.Instant;
023import javax.imageio.ImageIO;
024import javax.servlet.http.HttpServletResponse;
025import org.apache.wicket.WicketRuntimeException;
026import org.apache.wicket.util.lang.Args;
027
028/**
029 * Base class for dynamically generated ImageResources.
030 */
031public abstract class DynamicImageResource extends AbstractResource
032{
033        private static final long serialVersionUID = 1L;
034
035        /** The image type */
036        private String format = "png";
037
038        /** The last modified time of this resource */
039        private Instant lastModifiedTime;
040
041
042        /**
043         * Construct.
044         */
045        public DynamicImageResource()
046        {
047        }
048
049        /**
050         * Creates a dynamic resource from for the given locale
051         * 
052         * @param format
053         *            The image format ("png", "jpeg", etc)
054         */
055        public DynamicImageResource(String format)
056        {
057                setFormat(format);
058        }
059
060        /**
061         * @return Returns the image format.
062         */
063        public synchronized final String getFormat()
064        {
065                return format;
066        }
067
068        /**
069         * Sets the format of this resource
070         * 
071         * @param format
072         *            The format (jpg, png or gif..)
073         */
074        public synchronized final void setFormat(String format)
075        {
076                Args.notNull(format, "format");
077                this.format = format;
078        }
079
080        /**
081         * set the last modified time for this resource.
082         * 
083         * @param time
084         */
085        protected synchronized void setLastModifiedTime(Instant time)
086        {
087                lastModifiedTime = time;
088        }
089
090        /**
091         * @param image
092         *            The image to turn into data
093         * @return The image data for this dynamic image
094         */
095        protected byte[] toImageData(final BufferedImage image)
096        {
097                try
098                {
099                        // Create output stream
100                        final ByteArrayOutputStream out = new ByteArrayOutputStream();
101
102                        // Write image using any matching ImageWriter
103                        ImageIO.write(image, format, out);
104
105                        // Return the image data
106                        return out.toByteArray();
107                }
108                catch (IOException e)
109                {
110                        throw new WicketRuntimeException("Unable to convert dynamic image to stream", e);
111                }
112        }
113
114        /**
115         * Get image data for our dynamic image resource. If the subclass regenerates the data, it
116         * should set the {@link DynamicImageResource#setLastModifiedTime(Instant)} when it does so. This
117         * ensures that image caching works correctly.
118         * 
119         * @param attributes
120         *            the context bringing the request, response and the parameters
121         * 
122         * @return The image data for this dynamic image. {@code null} means there is no image and 404
123         *         (Not found) response will be return.
124         */
125        protected abstract byte[] getImageData(Attributes attributes);
126
127        protected void configureResponse(final ResourceResponse response, final Attributes attributes)
128        {
129        }
130
131        @Override
132        protected ResourceResponse newResourceResponse(final Attributes attributes)
133        {
134                final ResourceResponse response = new ResourceResponse();
135
136                if (lastModifiedTime != null)
137                {
138                        response.setLastModified(lastModifiedTime);
139                }
140                else
141                {
142                        response.setLastModified(Instant.now());
143                }
144
145                if (response.dataNeedsToBeWritten(attributes))
146                {
147                        response.setContentDisposition(ContentDisposition.INLINE);
148
149                        final byte[] imageData = getImageData(attributes);
150                        if (imageData == null)
151                        {
152                                response.setError(HttpServletResponse.SC_NOT_FOUND);
153                        }
154                        else
155                        {
156                                response.setContentType("image/" + getFormat());
157                                response.setWriteCallback(new WriteCallback()
158                                {
159                                        @Override
160                                        public void writeData(final Attributes attributes)
161                                        {
162                                                attributes.getResponse().write(imageData);
163                                        }
164                                });
165
166                                configureResponse(response, attributes);
167                        }
168                }
169
170                return response;
171        }
172}