/* * Copyright 2011-2013 HTTL Team. * * Licensed 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. */ package httl; import java.io.OutputStream; import java.io.Writer; import java.util.*; /** * Context. (API, Prototype, ThreadLocal, ThreadSafe) * <p/> * <pre> * Context context = Context.getContext(); * Object value = context.get(key); * </pre> * <p/> * Lookup variable: * <p/> * <pre> * if (value == null) value = puts.get(key); // context.put(key, value); * if (value == null) value = current.get(key); // render(current); * if (value == null) value = parent.get(key); // recursive above * * default resovler: * if (value == null) value = contextResovler.get(key); // parent, super, this, engine, out, level * if (value == null) value = servletResovler.get(key); // request, response, parameter, header, session, cookie, application * if (value == null) value = globalResovler.get(key); // GlobalResovler.put(key, value) * * non default resovler: * if (value == null) value = engineResovler.get(key); // httl.properties: key=value * if (value == null) value = systemResovler.get(key); // java -Dkey=value * if (value == null) value = environmentResolver.get(key); // export key=value * </pre> * * @author Liang Fei (liangfei0201 AT gmail DOT com) * @see httl.Template#render(Object, Object) * @see httl.spi.translators.templates.AbstractTemplate#render(Object, Object) */ public final class Context implements Map<String, Object> { // The context thread local holder. private static final ThreadLocal<Context> LOCAL = new ThreadLocal<Context>(); // The current thread. private final Thread thread; // The context level. private final int level; // The parent context. private final Context parent; // The current context. private Map<String, Object> current; // The current out. private Object out; // The current template. private Template template; // The current engine. private Engine engine; private Context(Context parent, Map<String, Object> current) { this.thread = parent == null ? Thread.currentThread() : parent.thread; this.level = parent == null ? 0 : parent.getLevel() + 1; this.parent = parent; setCurrent(current); } /** * Get the current context from thread local. * * @return current context */ public static Context getContext() { Context context = LOCAL.get(); if (context == null) { context = new Context(null, null); LOCAL.set(context); } return context; } /** * Push the current context to thread local. */ public static Context pushContext() { return pushContext(null); } /** * Push the current context to thread local. * * @param current - current variables */ public static Context pushContext(Map<String, Object> current) { Context context = new Context(getContext(), current); LOCAL.set(context); return context; } /** * Pop the current context from thread local, and restore parent context to thread local. */ public static void popContext() { Context context = LOCAL.get(); if (context != null) { Context parent = context.getParent(); if (parent != null) { LOCAL.set(parent); } else { LOCAL.remove(); } } } /** * Remove the current context from thread local. */ public static void removeContext() { LOCAL.remove(); } // Check the cross-thread use. private void checkThread() { if (Thread.currentThread() != thread) { throw new IllegalStateException("Don't cross-thread using the " + Context.class.getName() + " object, it's thread-local only. context thread: " + thread.getName() + ", current thread: " + Thread.currentThread().getName()); } } // Set the current context private void setCurrent(Map<String, Object> current) { if (current instanceof Context) { throw new IllegalArgumentException("Don't using the " + Context.class.getName() + " object as a parameters, it's implicitly delivery by thread-local. parameter context: " + ((Context) current).thread.getName() + ", current context: " + thread.getName()); } this.current = current; } /** * Get the context level. * * @return context level * @see #getContext() */ public int getLevel() { checkThread(); return level; } /** * Get the parent context. * * @return parent context * @see #getContext() */ public Context getParent() { checkThread(); return parent; } /** * Get the current template. * * @return current template * @see #getContext() */ public Template getTemplate() { checkThread(); return template; } /** * Set the current template. * * @param template - current template */ public Context setTemplate(Template template) { checkThread(); if (template != null) { setEngine(template.getEngine()); } this.template = template; return this; } /** * Get the current engine. * * @return current engine * @see #getContext() */ public Engine getEngine() { checkThread(); return engine; } /** * Set the current engine. * * @param engine - current engine */ public Context setEngine(Engine engine) { checkThread(); if (engine != null) { if (template != null && template.getEngine() != engine) { throw new IllegalStateException("Failed to set the context engine, because is not the same to template engine. template engine: " + template.getEngine().getName() + ", context engine: " + engine.getName() + ", template: " + template.getName() + ", context: " + thread.getName()); } if (parent != null && parent.getEngine() != engine) { parent.setEngine(engine); } if (this.engine == null) { setCurrent(engine.createContext(parent, current)); } } this.engine = engine; return this; } /** * Get the current out. * * @return current out * @see #getContext() */ public Object getOut() { checkThread(); return out; } /** * Set the current writer. * * @param out - current writer */ public Context setOut(Writer out) { checkThread(); this.out = out; return this; } /** * Set the current output stream. * * @param out - current output stream */ public Context setOut(OutputStream out) { checkThread(); this.out = out; return this; } /** * Get the variable value. * * @param key - variable key * @param defaultValue - default value * @return variable value * @see #getContext() */ public Object get(String key, Object defaultValue) { Object value = get(key); return value == null ? defaultValue : value; } // ==== Delegate the current context map ==== // public Object get(Object key) { checkThread(); return current == null ? null : current.get(key); } public int size() { checkThread(); return current == null ? 0 : current.size(); } public boolean isEmpty() { checkThread(); return current == null ? true : current.isEmpty(); } public boolean containsKey(Object key) { checkThread(); return current == null ? false : current.containsKey(key); } public boolean containsValue(Object value) { checkThread(); return current == null ? false : current.containsValue(value); } @SuppressWarnings("unchecked") public Set<String> keySet() { checkThread(); return current == null ? Collections.EMPTY_SET : current.keySet(); } @SuppressWarnings("unchecked") public Collection<Object> values() { checkThread(); return current == null ? Collections.EMPTY_SET : current.values(); } @SuppressWarnings("unchecked") public Set<Map.Entry<String, Object>> entrySet() { checkThread(); return current == null ? Collections.EMPTY_SET : current.entrySet(); } public Object put(String key, Object value) { checkThread(); if (current == null) { current = new HashMap<String, Object>(); } return current.put(key, value); } public void putAll(Map<? extends String, ? extends Object> m) { checkThread(); if (current == null) { current = new HashMap<String, Object>(); } current.putAll(m); } public Object remove(Object key) { checkThread(); return current == null ? null : current.remove(key); } public void clear() { checkThread(); if (current != null) { current.clear(); } } }