aboutsummaryrefslogtreecommitdiff
path: root/velocity-engine-core/src/main/java/org/apache/velocity/runtime/VelocimacroFactory.java
blob: b7961a5d6b88a3f65632867e3a2716ae3837c59b (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
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
package org.apache.velocity.runtime;

/*
 * 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.Template;
import org.apache.velocity.exception.VelocityException;
import org.apache.velocity.runtime.directive.Directive;
import org.apache.velocity.runtime.directive.Macro;
import org.apache.velocity.runtime.directive.VelocimacroProxy;
import org.apache.velocity.runtime.parser.node.Node;

import org.slf4j.Logger;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;

/**
 *  VelocimacroFactory.java
 *
 *   manages the set of VMs in a running Velocity engine.
 *
 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
 * @version $Id$
 */
public class VelocimacroFactory
{
    /**
     *  runtime services for this instance
     */
    private final RuntimeServices rsvc;

    /**
     *  the log for this instance
     */
    private Logger log = null;

    /**
     *  VMManager: deal with namespace management
     *  and actually keeps all the VM definitions
     */
    private VelocimacroManager vmManager = null;

    /**
     *  determines if replacement of global VMs are allowed
     *  controlled by  VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL
     */
    private boolean replaceAllowed = false;

    /**
     *  controls if new VMs can be added.  Set by
     *  VM_PERM_ALLOW_INLINE  Note the assumption that only
     *  through inline defs can this happen.
     *  additions through autoloaded VMs is allowed
     */
    private boolean addNewAllowed = true;

    /**
     *  sets if template-local namespace in used
     */
    private boolean templateLocal = false;

    /**
     *  determines if the libraries are auto-loaded
     *  when they change
     */
    private boolean autoReloadLibrary = false;

    /**
     *  vector of the library names
     */
    private List<String> macroLibVec = null;

    /**
     *  map of the library Template objects
     *  used for reload determination
     */
    private Map<String, Twonk> libModMap;

    /**
     *  C'tor for the VelociMacro factory.
     *
     * @param rsvc Reference to a runtime services object.
     */
    public VelocimacroFactory(final RuntimeServices rsvc)
    {
        this.rsvc = rsvc;

        /*
         *  we always access in a synchronized(), so we
         *  can use an unsynchronized hashmap
         */
        libModMap = new HashMap<>();
        vmManager = new VelocimacroManager(rsvc);
    }

    /**
     *  initialize the factory - setup all permissions
     *  load all global libraries.
     */
    public void initVelocimacro()
    {
        /*
         *  maybe I'm just paranoid...
         */
        synchronized(this)
        {
            log = rsvc.getLog("macro");
            log.trace("initialization starting.");

            /*
             *   allow replacements while we add the libraries, if exist
             */
            setReplacementPermission(true);

            /*
             *  add all library macros to the global namespace
             */

            vmManager.setNamespaceUsage(false);

            /*
             *  now, if there is a global or local libraries specified, use them.
             *  All we have to do is get the template. The template will be parsed;
             *  VM's  are added during the parse phase
             */

             Object libfiles = rsvc.getProperty(RuntimeConstants.VM_LIBRARY);

             if (libfiles == null)
             {
                 log.debug("\"{}\" is not set. Trying default library: {}", RuntimeConstants.VM_LIBRARY, RuntimeConstants.VM_LIBRARY_DEFAULT);

                 // try the default library.
                 if (rsvc.getLoaderNameForResource(RuntimeConstants.VM_LIBRARY_DEFAULT) != null)
                 {
                     libfiles = RuntimeConstants.VM_LIBRARY_DEFAULT;
                 }
                 else
                 {
                     // try the old default library
                     log.debug("Default library {} not found. Trying old default library: {}", RuntimeConstants.VM_LIBRARY_DEFAULT, DeprecatedRuntimeConstants.OLD_VM_LIBRARY_DEFAULT);
                     if (rsvc.getLoaderNameForResource(RuntimeConstants.OLD_VM_LIBRARY_DEFAULT) != null)
                     {
                         libfiles = RuntimeConstants.OLD_VM_LIBRARY_DEFAULT;
                     }
                     else
                     {
                         log.debug("Old default library {} not found.", DeprecatedRuntimeConstants.OLD_VM_LIBRARY_DEFAULT);
                     }
                 }
             }

             if(libfiles != null)
             {
                 macroLibVec = new ArrayList<>();
                 if (libfiles instanceof Vector)
                 {
                     macroLibVec.addAll((Vector<String>)libfiles);
                 }
                 else if (libfiles instanceof String)
                 {
                     macroLibVec.add((String)libfiles);
                 }

                 for (String lib : macroLibVec)
                 {
                     /*
                      * only if it's a non-empty string do we bother
                      */

                     if (StringUtils.isNotEmpty(lib))
                     {
                         /*
                          *  let the VMManager know that the following is coming
                          *  from libraries - need to know for auto-load
                          */

                         vmManager.setRegisterFromLib(true);

                         log.debug("adding VMs from VM library: {}", lib);

                         try
                         {
                             Template template = rsvc.getTemplate(lib);

                             /*
                              *  save the template.  This depends on the assumption
                              *  that the Template object won't change - currently
                              *  this is how the Resource manager works
                              */

                             Twonk twonk = new Twonk();
                             twonk.template = template;
                             twonk.modificationTime = template.getLastModified();
                             libModMap.put(lib, twonk);
                         }
                         catch (Exception e)
                         {
                             String msg = "Velocimacro: Error using VM library: " + lib;
                             log.error(msg, e);
                             throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace());
                         }

                         log.trace("VM library registration complete.");

                         vmManager.setRegisterFromLib(false);
                     }
                 }
             }

            /*
             *   now, the permissions
             */


            /*
             *  allowinline: anything after this will be an inline macro, I think
             *  there is the question if a #include is an inline, and I think so
             *
             *  default = true
             */
            setAddMacroPermission(true);

            if (!rsvc.getBoolean( RuntimeConstants.VM_PERM_ALLOW_INLINE, true))
            {
                setAddMacroPermission(false);

                log.debug("allowInline = false: VMs can NOT be defined inline in templates");
            }
            else
            {
                log.debug("allowInline = true: VMs can be defined inline in templates");
            }

            /*
             *  allowInlineToReplaceGlobal: allows an inline VM , if allowed at all,
             *  to replace an existing global VM
             *
             *  default = false
             */
            setReplacementPermission(false);

            if (rsvc.getBoolean(
                 RuntimeConstants.VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL, false))
            {
                setReplacementPermission(true);

                log.debug("allowInlineToOverride = true: VMs " +
                    "defined inline may replace previous VM definitions");
            }
            else
            {
                log.debug("allowInlineToOverride = false: VMs " +
                    "defined inline may NOT replace previous VM definitions");
            }

            /*
             * now turn on namespace handling as far as permissions allow in the
             * manager, and also set it here for gating purposes
             */
            vmManager.setNamespaceUsage(true);

            /*
             *  template-local inline VM mode: default is off
             */
            setTemplateLocalInline(rsvc.getBoolean(
                RuntimeConstants.VM_PERM_INLINE_LOCAL, false));

            if (getTemplateLocalInline())
            {
                log.debug("allowInlineLocal = true: VMs " +
                    "defined inline will be local to their defining template only.");
            }
            else
            {
                log.debug("allowInlineLocal = false: VMs " +
                    "defined inline will be global in scope if allowed.");
            }

            vmManager.setTemplateLocalInlineVM(getTemplateLocalInline());

            /*
             *  autoload VM libraries
             */
            setAutoload(rsvc.getBoolean(RuntimeConstants.VM_LIBRARY_AUTORELOAD, false));

            if (getAutoload())
            {
                log.debug("autoload on: VM system " +
                     "will automatically reload global library macros");
            }
            else
            {
                log.debug("autoload off: VM system " +
                      "will not automatically reload global library macros");
            }

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

    /**
     * Adds a macro to the factory.
     *
     * @param name Name of the Macro to add.
     * @param macroBody root node of the parsed macro AST
     * @param macroArgs  Array of macro arguments, containing the
     *        #macro() arguments and default values.  the 0th is the name.
     * @param definingTemplate template containing the macro definition
     * @return true if Macro was registered successfully.
     * @since 1.6
     */
    public boolean addVelocimacro(String name, Node macroBody,
            List<Macro.MacroArg> macroArgs, Template definingTemplate)
    {
        // Called by RuntimeInstance.addVelocimacro

    	/*
         * maybe we should throw an exception, maybe just tell
         * the caller like this...
         *
         * I hate this: maybe exceptions are in order here...
         * They definitely would be if this was only called by directly
         * by users, but Velocity calls this internally.
         */
        if (name == null || macroBody == null || macroArgs == null ||
            definingTemplate == null)
        {
            String msg = "VM '"+name+"' addition rejected: ";
            if (name == null)
            {
                msg += "name";
            }
            else if (macroBody == null)
            {
                msg += "macroBody";
            }
            else if (macroArgs == null)
            {
                msg += "macroArgs";
            }
            else
            {
                msg += "sourceTemplate";
            }
            msg += " argument was null";
            log.error(msg);
            throw new NullPointerException(msg);
        }

        /*
         *  see if the current ruleset allows this addition
         */

        if (!canAddVelocimacro(name, definingTemplate))
        {
            return false;
        }

        synchronized(this)
        {
            vmManager.addVM(name, macroBody, macroArgs, definingTemplate, replaceAllowed);
        }
        log.debug("added VM {}: source={}", name, definingTemplate);
        return true;
    }


    /**
     *  determines if a given macro/namespace (name, source) combo is allowed
     *  to be added
     *
     *  @param name Name of VM to add
     *  @param definingTemplate template containing the source of the macro
     *  @return true if it is allowed to be added, false otherwise
     */
    private synchronized boolean canAddVelocimacro(String name, Template definingTemplate)
    {
        /*
         *  short circuit and do it if autoloader is on, and the
         *  template is one of the library templates
         */

        if (autoReloadLibrary && (macroLibVec != null))
        {
            if( macroLibVec.contains(definingTemplate.getName()) )
                return true;
        }


        /*
         * maybe the rules should be in manager?  I dunno. It's to manage
         * the namespace issues first, are we allowed to add VMs at all?
         * This trumps all.
         */
        if (!addNewAllowed)
        {
            log.warn("VM addition rejected: {}: inline VelociMacros not allowed.", name);
            return false;
        }

        /*
         *  are they local in scope?  Then it is ok to add.
         */
        if (!templateLocal)
        {
            /*
             * otherwise, if we have it already in global namespace, and they can't replace
             * since local templates are not allowed, the global namespace is implied.
             *  remember, we don't know anything about namespace management here, so lets
             *  note do anything fancy like trying to give it the global namespace here
             *
             *  so if we have it, and we aren't allowed to replace, bail
             */
            if (!replaceAllowed && isVelocimacro(name, definingTemplate))
            {
                /*
                 * Concurrency fix: the log entry was changed to debug scope because it
                 * causes false alarms when several concurrent threads simultaneously (re)parse
                 * some macro
                 */
                log.debug("VM addition rejected: {}: inline not allowed to replace existing VM", name);
                return false;
            }
        }

        return true;
    }

    /**
     * Tells the world if a given directive string is a Velocimacro
     * @param vm Name of the Macro.
     * @param template Source template from which the macro should be loaded.
     * @return True if the given name is a macro.
     */
    public boolean isVelocimacro(String vm, Template template)
    {
        // synchronization removed
        return(vmManager.get(vm, null, template) != null);
    }

    /**
     *  actual factory: creates a Directive that will
     *  behave correctly wrt getting the framework to
     *  dig out the correct # of args
     * @param vmName Name of the Macro.
     * @param renderingTemplate destination template
     * @param sourceTemplate Source template from which the macro should be loaded.
     * @return A directive representing the Macro.
     */
     public Directive getVelocimacro(String vmName, Template renderingTemplate, Template sourceTemplate)
     {
        VelocimacroProxy vp = null;

        vp = vmManager.get(vmName, renderingTemplate, sourceTemplate);

        /*
         * if this exists, and autoload is on, we need to check where this VM came from
         */

        if (vp != null && autoReloadLibrary )
        {
            synchronized (this)
            {
                /*
                 * see if this VM came from a library. Need to pass sourceTemplate in the event
                 * namespaces are set, as it could be masked by local
                 */

                String lib = vmManager.getLibraryName(vmName, sourceTemplate);

                if (lib != null)
                {
                    try
                    {
                        /*
                         * get the template from our map
                         */

                        Twonk tw = libModMap.get(lib);

                        if (tw != null)
                        {
                            Template template = tw.template;

                            /*
                             * now, compare the last modified time of the resource with the last
                             * modified time of the template if the file has changed, then reload.
                             * Otherwise, we should be ok.
                             */

                            long tt = tw.modificationTime;
                            long ft = template.getResourceLoader().getLastModified(template);

                            if (ft > tt)
                            {
                                log.debug("auto-reloading VMs from VM library: {}", lib);

                                /*
                                 * when there are VMs in a library that invoke each other, there are
                                 * calls into getVelocimacro() from the init() process of the VM
                                 * directive. To stop the infinite loop we save the current time
                                 * reported by the resource loader and then be honest when the
                                 * reload is complete
                                 */

                                tw.modificationTime = ft;

                                template = rsvc.getTemplate(lib);

                                /*
                                 * and now we be honest
                                 */

                                tw.template = template;
                                tw.modificationTime = template.getLastModified();

                                /*
                                 * note that we don't need to put this twonk
                                 * back into the map, as we can just use the
                                 * same reference and this block is synchronized
                                 */
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        String msg = "Velocimacro: Error using VM library: " + lib;
                        log.error(msg, e);
                        throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace());
                    }

                    vp = vmManager.get(vmName, sourceTemplate, renderingTemplate);
                }
            }
        }

        return vp;
    }

    /**
     * sets permission to have VMs local in scope to their declaring template note that this is
     * really taken care of in the VMManager class, but we need it here for gating purposes in addVM
     * eventually, I will slide this all into the manager, maybe.
     */
    private void setTemplateLocalInline(boolean b)
    {
        templateLocal = b;
    }

    private boolean getTemplateLocalInline()
    {
        return templateLocal;
    }

    /**
     * sets the permission to add new macros
     */
    private boolean setAddMacroPermission(final boolean addNewAllowed)
    {
        boolean b = this.addNewAllowed;
        this.addNewAllowed = addNewAllowed;
        return b;
    }

    /**
     * sets the permission for allowing addMacro() calls to replace existing VM's
     */
    private boolean setReplacementPermission(boolean arg)
    {
        boolean b = replaceAllowed;
        replaceAllowed = arg;
        vmManager.setInlineReplacesGlobal(arg);
        return b;
    }

    /**
     *  set the switch for automatic reloading of
     *  global library-based VMs
     */
    private void setAutoload(boolean b)
    {
        autoReloadLibrary = b;
    }

    /**
     *  get the switch for automatic reloading of
     *  global library-based VMs
     */
    private boolean getAutoload()
    {
        return autoReloadLibrary;
    }

    /**
     * small container class to hold the tuple
     * of a template and modification time.
     * We keep the modification time so we can
     * 'override' it on a reload to prevent
     * recursive reload due to inter-calling
     * VMs in a library
     */
    private static class Twonk
    {
        /** Template kept in this container. */
        public Template template;

        /** modification time of the template. */
        public long modificationTime;
    }
}