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