aboutsummaryrefslogtreecommitdiff
path: root/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/StringResourceLoader.java
blob: efe722504ed232a0139bd29dce5e3916112bb746 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
package org.apache.velocity.runtime.resource.loader;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import org.apache.commons.lang3.StringUtils;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.exception.VelocityException;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.resource.Resource;
import org.apache.velocity.runtime.resource.util.StringResource;
import org.apache.velocity.runtime.resource.util.StringResourceRepository;
import org.apache.velocity.runtime.resource.util.StringResourceRepositoryImpl;
import org.apache.velocity.util.ClassUtils;
import org.apache.velocity.util.ExtProperties;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * Resource loader that works with Strings. Users should manually add
 * resources to the repository that is used by the resource loader instance.
 *
 * Below is an example configuration for this loader.
 * Note that 'repository.class' is not necessary;
 * if not provided, the factory will fall back on using
 * {@link StringResourceRepositoryImpl} as the default.
 * <pre>
 * resource.loader = string
 * string.resource.loader.description = Velocity StringResource loader
 * string.resource.loader.class = org.apache.velocity.runtime.resource.loader.StringResourceLoader
 * string.resource.loader.repository.class = org.apache.velocity.runtime.resource.loader.StringResourceRepositoryImpl
 * </pre>
 * Resources can be added to the repository like this:
 * <pre><code>
 *   StringResourceRepository repo = StringResourceLoader.getRepository();
 *
 *   String myTemplateName = "/some/imaginary/path/hello.vm";
 *   String myTemplate = "Hi, ${username}... this is some template!";
 *   repo.putStringResource(myTemplateName, myTemplate);
 * </code></pre>
 *
 * After this, the templates can be retrieved as usual.
 * <br>
 * <p>If there will be multiple StringResourceLoaders used in an application,
 * you should consider specifying a 'string.resource.loader.repository.name = foo'
 * property in order to keep you string resources in a non-default repository.
 * This can help to avoid conflicts between different frameworks or components
 * that are using StringResourceLoader.
 * You can then retrieve your named repository like this:
 * <pre><code>
 *   StringResourceRepository repo = StringResourceLoader.getRepository("foo");
 * </code></pre>
 * and add string resources to the repo just as in the previous example.
 * </p>
 * <p>If you have concerns about memory leaks or for whatever reason do not wish
 * to have your string repository stored statically as a class member, then you
 * should set 'string.resource.loader.repository.static = false' in your properties.
 * This will tell the resource loader that the string repository should be stored
 * in the Velocity application attributes.  To retrieve the repository, do:
 * <pre><code>
 *   StringResourceRepository repo = velocityEngine.getApplicationAttribute("foo");
 * </code></pre>
 * If you did not specify a name for the repository, then it will be stored under the
 * class name of the repository implementation class (for which the default is
 * 'org.apache.velocity.runtime.resource.util.StringResourceRepositoryImpl').
 * Incidentally, this is also true for the default statically stored repository.
 * </p>
 * <p>Whether your repository is stored statically or in Velocity's application
 * attributes, you can also manually create and set it prior to Velocity
 * initialization.  For a static repository, you can do something like this:
 * <pre><code>
 *   StringResourceRepository repo = new MyStringResourceRepository();
 *   repo.magicallyAddSomeStringResources();
 *   StringResourceLoader.setRepository("foo", repo);
 * </code></pre>
 * Or for a non-static repository:
 * <pre><code>
 *   StringResourceRepository repo = new MyStringResourceRepository();
 *   repo.magicallyAddSomeStringResources();
 *   velocityEngine.setApplicationAttribute("foo", repo);
 * </code></pre>
 * Then, assuming the 'string.resource.loader.repository.name' property is
 * set to 'some.name', the StringResourceLoader will use that already created
 * repository, rather than creating a new one.
 * </p>
 *
 * @author <a href="mailto:eelco.hillenius@openedge.nl">Eelco Hillenius</a>
 * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
 * @author Nathan Bubna
 * @version $Id$
 * @since 1.5
 */
public class StringResourceLoader extends ResourceLoader
{
    /**
     * Key to determine whether the repository should be set as the static one or not.
     * @since 1.6
     */
    public static final String REPOSITORY_STATIC = "repository.static";

    /**
     * By default, repositories are stored statically (shared across the VM).
     * @since 1.6
     */
    public static final boolean REPOSITORY_STATIC_DEFAULT = true;

    /** Key to look up the repository implementation class. */
    public static final String REPOSITORY_CLASS = "repository.class";

    /** The default implementation class. */
    public static final String REPOSITORY_CLASS_DEFAULT =
        StringResourceRepositoryImpl.class.getName();

    /**
     * Key to look up the name for the repository to be used.
     * @since 1.6
     */
    public static final String REPOSITORY_NAME = "repository.name";

    /** The default name for string resource repositories
     * ('org.apache.velocity.runtime.resource.util.StringResourceRepository').
     * @since 1.6
     */
    public static final String REPOSITORY_NAME_DEFAULT =
        StringResourceRepository.class.getName();

    /** Key to look up the repository char encoding. */
    public static final String REPOSITORY_ENCODING = "repository.encoding";

    protected static final Map STATIC_REPOSITORIES =
        Collections.synchronizedMap(new HashMap());

    /**
     * Returns a reference to the default static repository.
     */
    public static StringResourceRepository getRepository()
    {
        return getRepository(REPOSITORY_NAME_DEFAULT);
    }

    /**
     * Returns a reference to the repository stored statically under the
     * specified name.
     * @since 1.6
     */
    public static StringResourceRepository getRepository(String name)
    {
        return (StringResourceRepository)STATIC_REPOSITORIES.get(name);
    }

    /**
     * Sets the specified {@link StringResourceRepository} in static storage
     * under the specified name.
     * @since 1.6
     */
    public static void setRepository(String name, StringResourceRepository repo)
    {
        STATIC_REPOSITORIES.put(name, repo);
    }

    /**
     * Removes the {@link StringResourceRepository} stored under the specified
     * name.
     * @since 1.6
     */
    public static StringResourceRepository removeRepository(String name)
    {
        return (StringResourceRepository)STATIC_REPOSITORIES.remove(name);
    }

    /**
     * Removes all statically stored {@link StringResourceRepository}s.
     * @since 1.6
     */
    public static void clearRepositories()
    {
        STATIC_REPOSITORIES.clear();
    }


    // the repository used internally by this resource loader
    protected StringResourceRepository repository;


    /**
     * @see ResourceLoader#init(org.apache.velocity.util.ExtProperties)
     */
    public void init(final ExtProperties configuration)
    {
        log.trace("StringResourceLoader: initialization starting.");

        // get the repository configuration info
        String repoClass = configuration.getString(REPOSITORY_CLASS, REPOSITORY_CLASS_DEFAULT);
        String repoName = configuration.getString(REPOSITORY_NAME, REPOSITORY_NAME_DEFAULT);
        boolean isStatic = configuration.getBoolean(REPOSITORY_STATIC, REPOSITORY_STATIC_DEFAULT);
        String encoding = configuration.getString(REPOSITORY_ENCODING);

        // look for an existing repository of that name and isStatic setting
        if (isStatic)
        {
            this.repository = getRepository(repoName);
            if (repository != null)
            {
                log.debug("Loaded repository '{}' from static repo store", repoName);
            }
        }
        else
        {
            this.repository = (StringResourceRepository)rsvc.getApplicationAttribute(repoName);
            if (repository != null)
            {
                log.debug("Loaded repository '{}' from application attributes", repoName);
            }
        }

        if (this.repository == null)
        {
            // since there's no repository under the repo name, create a new one
            this.repository = createRepository(repoClass, encoding);

            // and store it according to the isStatic setting
            if (isStatic)
            {
                setRepository(repoName, this.repository);
            }
            else
            {
                rsvc.setApplicationAttribute(repoName, this.repository);
            }
        }
        else
        {
            // ok, we already have a repo
            // warn them if they are trying to change the class of the repository
            if (!this.repository.getClass().getName().equals(repoClass))
            {
                log.debug("Cannot change class of string repository '{}' from {} to {}." +
                          " The change will be ignored.",
                          repoName, this.repository.getClass().getName(), repoClass);
            }

            // allow them to change the default encoding of the repo
            if (encoding != null &&
                !this.repository.getEncoding().equals(encoding))
            {
                log.debug("Changing the default encoding of string repository '{}' from {} to {}",
                          repoName, this.repository.getEncoding(), encoding);
                this.repository.setEncoding(encoding);
            }
        }

        log.trace("StringResourceLoader: initialization complete.");
    }

    /**
     * @since 1.6
     */
    public StringResourceRepository createRepository(final String className,
                                                     final String encoding)
    {
        log.debug("Creating string repository using class {}...", className);

        StringResourceRepository repo;
        try
        {
            repo = (StringResourceRepository) ClassUtils.getNewInstance(className);
        }
        catch (ClassNotFoundException cnfe)
        {
            throw new VelocityException("Could not find '" + className + "'", cnfe);
        }
        catch (IllegalAccessException iae)
        {
            throw new VelocityException("Could not access '" + className + "'", iae);
        }
        catch (InstantiationException ie)
        {
            throw new VelocityException("Could not instantiate '" + className + "'", ie);
        }

        if (encoding != null)
        {
            repo.setEncoding(encoding);
        }
        else
        {
            repo.setEncoding(RuntimeConstants.ENCODING_DEFAULT);
        }

        log.debug("Default repository encoding is {}", repo.getEncoding());
        return repo;
    }

    /**
     * Overrides superclass for better performance.
     * @since 1.6
     */
    public boolean resourceExists(final String name)
    {
        if (name == null)
        {
            return false;
        }
        return (this.repository.getStringResource(name) != null);
    }

    /**
     * Get a reader so that the Runtime can build a
     * template with it.
     *
     * @param name name of template to get.
     * @param encoding asked encoding
     * @return Reader containing the template.
     * @throws ResourceNotFoundException Ff template not found
     *         in the RepositoryFactory.
     * @since 2.0
     */
    public Reader getResourceReader(String name, String encoding)
            throws ResourceNotFoundException
    {
        if (StringUtils.isEmpty(name))
        {
            throw new ResourceNotFoundException("No template name provided");
        }

        StringResource resource = this.repository.getStringResource(name);

        if(resource == null)
        {
            throw new ResourceNotFoundException("Could not locate resource '" + name + "'");
        }

        byte [] byteArray = null;
        InputStream rawStream = null;

        try
        {
            byteArray = resource.getBody().getBytes(resource.getEncoding());
            rawStream = new ByteArrayInputStream(byteArray);
            return new InputStreamReader(rawStream, resource.getEncoding());
        }
        catch(UnsupportedEncodingException ue)
        {
            if (rawStream != null)
            {
                try
                {
                    rawStream.close();
                }
                catch (IOException ioe) {}
            }
            throw new VelocityException("Could not convert String using encoding " + resource.getEncoding(), ue);
        }
    }

    /**
     * @see ResourceLoader#isSourceModified(org.apache.velocity.runtime.resource.Resource)
     */
    public boolean isSourceModified(final Resource resource)
    {
        StringResource original = null;
        boolean result = true;

        original = this.repository.getStringResource(resource.getName());

        if (original != null)
        {
            result =  original.getLastModified() != resource.getLastModified();
        }

        return result;
    }

    /**
     * @see ResourceLoader#getLastModified(org.apache.velocity.runtime.resource.Resource)
     */
    public long getLastModified(final Resource resource)
    {
        StringResource original = null;

        original = this.repository.getStringResource(resource.getName());

        return (original != null)
                ? original.getLastModified()
                : 0;
    }

}