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.util.template;
018
019import java.io.IOException;
020import java.util.Locale;
021import java.util.Map;
022import java.util.Objects;
023
024import org.apache.wicket.Application;
025import org.apache.wicket.util.io.Streams;
026import org.apache.wicket.util.lang.Packages;
027import org.apache.wicket.util.resource.IResourceStream;
028import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
029import org.apache.wicket.core.util.resource.locator.ResourceStreamLocator;
030import org.apache.wicket.util.string.interpolator.MapVariableInterpolator;
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033
034
035/**
036 * A <code>String</code> resource that can be appended to.
037 * 
038 * @author Eelco Hillenius
039 * @since 1.2.6
040 */
041public class PackageTextTemplate extends TextTemplate
042{
043        /** log. */
044        private static final Logger log = LoggerFactory.getLogger(PackageTextTemplate.class);
045
046        private static final long serialVersionUID = 1L;
047
048        /** The content type used if not provided in the constructor */
049        public static final String DEFAULT_CONTENT_TYPE = "text";
050
051        /** The encoding used if not provided in the constructor */
052        public static final String DEFAULT_ENCODING = null;
053
054        /** contents */
055        private final StringBuilder buffer = new StringBuilder();
056
057        private final Class<?> scope;
058
059        private final String fileName;
060
061        private String encoding;
062
063        /**
064         * Constructor.
065         * 
066         * @param clazz
067         *            the <code>Class</code> to be used for retrieving the classloader for loading the
068         *            <code>PackagedTextTemplate</code>
069         * @param fileName
070         *            the name of the file, relative to the <code>clazz</code> position
071         */
072        public PackageTextTemplate(final Class<?> clazz, final String fileName)
073        {
074                this(clazz, fileName, DEFAULT_CONTENT_TYPE);
075        }
076
077        /**
078         * Constructor.
079         * 
080         * @param clazz
081         *            the <code>Class</code> to be used for retrieving the classloader for loading the
082         *            <code>PackagedTextTemplate</code>
083         * @param fileName
084         *            the name of the file, relative to the <code>clazz</code> position
085         * @param contentType
086         *            the mime type of this resource, such as "<code>image/jpeg</code>" or "
087         *            <code>text/html</code>"
088         */
089        public PackageTextTemplate(final Class<?> clazz, final String fileName, final String contentType)
090        {
091                this(clazz, fileName, contentType, DEFAULT_ENCODING);
092        }
093
094        /**
095         * Constructor.
096         * 
097         * @param clazz
098         *            the <code>Class</code> to be used for retrieving the classloader for loading the
099         *            <code>PackagedTextTemplate</code>
100         * @param fileName
101         *            the name of the file, relative to the <code>clazz</code> position
102         * @param contentType
103         *            the mime type of this resource, such as "<code>image/jpeg</code>" or "
104         *            <code>text/html</code>"
105         * @param encoding
106         *            the file's encoding, for example, "<code>UTF-8</code>"
107         */
108        public PackageTextTemplate(final Class<?> clazz, final String fileName,
109                final String contentType, final String encoding)
110        {
111                this(clazz, fileName, null, null, null, contentType, encoding);
112        }
113
114        /**
115         * Constructor.
116         *
117         * @param clazz
118         *            the <code>Class</code> to be used for retrieving the classloader for loading the
119         *            <code>PackagedTextTemplate</code>
120         * @param fileName
121         *            the name of the file, relative to the <code>clazz</code> position
122         * @param style
123         *            Any resource style, such as a skin style (see {@link org.apache.wicket.Session})
124         * @param variation
125         *            The template's variation (of the style)
126         * @param locale
127         *            The locale of the resource to load
128         * @param contentType
129         *            the mime type of this resource, such as "<code>image/jpeg</code>" or "
130         *            <code>text/html</code>"
131         * @param encoding
132         *            the file's encoding, for example, "<code>UTF-8</code>"
133         */
134        public PackageTextTemplate(final Class<?> clazz, final String fileName, final String style, final String variation,
135                final Locale locale, final String contentType, final String encoding)
136        {
137                super(contentType);
138
139                this.scope = clazz;
140                this.fileName = fileName;
141                this.encoding = encoding;
142
143                setStyle(style);
144                setVariation(variation);
145                setLocale(locale);
146        }
147
148        @Override
149        public void setStyle(String style)
150        {
151                if (Objects.equals(style, getStyle()) == false)
152                {
153                        buffer.setLength(0);
154                }
155                super.setStyle(style);
156        }
157
158        @Override
159        public void setLocale(Locale locale)
160        {
161                if (Objects.equals(locale, getLocale()) == false)
162                {
163                        buffer.setLength(0);
164                }
165                super.setLocale(locale);
166        }
167
168        @Override
169        public void setVariation(String variation)
170        {
171                if (Objects.equals(variation, getVariation()) == false)
172                {
173                        buffer.setLength(0);
174                }
175                super.setVariation(variation);
176        }
177
178        public void setEncoding(String encoding)
179        {
180                if (Objects.equals(encoding, this.encoding) == false)
181                {
182                        buffer.setLength(0);
183                }
184                this.encoding = encoding == null ? DEFAULT_ENCODING : encoding;
185        }
186
187        /**
188         * Loads the template if it is not loaded yet
189         */
190        private void load() {
191                if (buffer.length() == 0)
192                {
193                        String path = Packages.absolutePath(scope, fileName);
194
195                        Application app = Application.get();
196
197                        // first try default class loading locator to find the resource
198                        IResourceStream stream = app.getResourceSettings()
199                                .getResourceStreamLocator()
200                                .locate(scope, path, getStyle(), getVariation(), getLocale(), null, false);
201
202                        if (stream == null)
203                        {
204                                // if the default locator didn't find the resource then fallback
205                                stream = new ResourceStreamLocator().locate(scope, path, getStyle(), getVariation(), getLocale(), null, false);
206                        }
207
208                        if (stream == null)
209                        {
210                                throw new IllegalArgumentException("resource " + fileName + " not found for scope " +
211                                        scope + " (path = " + path + ")");
212                        }
213
214                        setLastModified(stream.lastModifiedTime());
215
216                        try
217                        {
218                                if (encoding != null)
219                                {
220                                        buffer.append(Streams.readString(stream.getInputStream(), encoding));
221                                }
222                                else
223                                {
224                                        buffer.append(Streams.readString(stream.getInputStream()));
225                                }
226                        }
227                        catch (IOException e)
228                        {
229                                throw new RuntimeException(e);
230                        }
231                        catch (ResourceStreamNotFoundException e)
232                        {
233                                throw new RuntimeException(e);
234                        }
235                        finally
236                        {
237                                try
238                                {
239                                        stream.close();
240                                }
241                                catch (IOException e)
242                                {
243                                        log.error(e.getMessage(), e);
244                                }
245                        }
246                }
247        }
248
249        /**
250         * @see org.apache.wicket.util.resource.AbstractStringResourceStream#getString()
251         */
252        @Override
253        public String getString()
254        {
255                load();
256                return buffer.toString();
257        }
258
259        /**
260         * Interpolates a <code>Map</code> of variables with the content and replaces the content with
261         * the result. Variables are denoted in the <code>String</code> by the
262         * <code>syntax ${variableName}</code>. The contents will be altered by replacing each variable
263         * of the form <code>${variableName}</code> with the value returned by
264         * <code>variables.getValue("variableName")</code>.
265         * <p>
266         * WARNING: there is no going back to the original contents after the interpolation is done. If
267         * you need to do different interpolations on the same original contents, use the method
268         * {@link #asString(Map)} instead.
269         * </p>
270         * 
271         * @param variables
272         *            a <code>Map</code> of variables to interpolate
273         * @return this for chaining
274         */
275        @Override
276        public final TextTemplate interpolate(Map<String, ?> variables)
277        {
278                if (variables != null)
279                {
280                        load();
281                        String result = new MapVariableInterpolator(buffer.toString(), variables).toString();
282                        buffer.setLength(0);
283                        buffer.append(result);
284                }
285                return this;
286        }
287
288
289}