1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ondevicepersonalization.services.display.velocity;
18 
19 import android.annotation.NonNull;
20 import android.content.Context;
21 import android.os.PersistableBundle;
22 
23 import org.apache.velocity.VelocityContext;
24 import org.apache.velocity.app.VelocityEngine;
25 import org.apache.velocity.app.event.EventCartridge;
26 import org.apache.velocity.app.event.ReferenceInsertionEventHandler;
27 
28 import java.util.Properties;
29 
30 /**
31  * VelocityEngine factory.
32  */
33 public class VelocityEngineFactory {
34     private static VelocityEngine sSingleton;
35 
36     private static final int MAX_FOREACH = 100;
37 
VelocityEngineFactory()38     private VelocityEngineFactory() {
39     }
40 
41     /**
42      * Returns a singleton of VelocityEngine.
43      */
44     @NonNull
getVelocityEngine(Context context)45     public static synchronized VelocityEngine getVelocityEngine(Context context) {
46         synchronized (VelocityEngineFactory.class) {
47             if (sSingleton == null) {
48                 sSingleton = new VelocityEngine();
49                 Properties props = getProperties(context);
50                 sSingleton.init(props);
51             }
52             return sSingleton;
53         }
54     }
55 
56     /**
57      * Create a Velocity context populating it with the given bundle.
58      */
59     @NonNull
createVelocityContext( PersistableBundle bundle)60     public static org.apache.velocity.context.Context createVelocityContext(
61             PersistableBundle bundle) {
62         VelocityContext ctx = new VelocityContext();
63         OnDevicePersonalizationVelocityTool tool = new OnDevicePersonalizationVelocityTool();
64         // TODO(b/263180569): Determine what name to provide this as.
65         ctx.put("tool", tool);
66         EventCartridge eventCartridge = new EventCartridge();
67         eventCartridge.attachToContext(ctx);
68         eventCartridge.addReferenceInsertionEventHandler(new ReferenceInsertionEventHandler() {
69             @Override
70             public Object referenceInsert(org.apache.velocity.context.Context context,
71                     String reference, Object value) {
72                 // TODO(b/263180569): Implement default encoding behavior.
73                 return value;
74             }
75         });
76 
77         for (String key : bundle.keySet()) {
78             ctx.put(key, bundle.get(key));
79         }
80         return ctx;
81     }
82 
83     @NonNull
getProperties(Context context)84     private static Properties getProperties(Context context) {
85         Properties props = new Properties();
86         // Set default template path to cache dir.
87         props.put("file.resource.loader.path", context.getCacheDir().getAbsolutePath());
88 
89         // Set max parse depth to 1. Templates cannot use other template files.
90         props.put("directive.parse.max.depth", 1);
91 
92         // TODO(b/262001121): Set to SecureUberspector for now. Consider using custom uberspector.
93         props.put("introspector.uberspect.class",
94                 "org.apache.velocity.util.introspection.SecureUberspector");
95 
96         // Limit the maximum allowed number of loops for a #foreach() statement.
97         props.put("directive.foreach.max_loops", MAX_FOREACH);
98 
99         // Default input encoding for templates.
100         props.put("resource.default_encoding", "UTF-8");
101 
102         // Do not allow defining global macros.
103         props.put("velocimacro.permissions.allow.inline.local.scope", true);
104 
105         return props;
106     }
107 }
108