/* * Copyright 2012 astamuse company,Ltd. * * 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 com.astamuse.asta4d; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.Callable; import org.jsoup.nodes.Element; import com.astamuse.asta4d.data.ContextBindData; import com.astamuse.asta4d.data.ContextDataHolder; import com.astamuse.asta4d.data.InjectUtil; import com.astamuse.asta4d.extnode.ExtNodeConstants; import com.astamuse.asta4d.snippet.interceptor.SnippetInitializeInterceptor; import com.astamuse.asta4d.util.DelegatedContextMap; public class Context { public final static String SCOPE_GLOBAL = "global"; public final static String SCOPE_DEFAULT = "default"; public final static String SCOPE_ATTR = "attr"; public final static String SCOPE_EXT_ATTR = "ext_attr"; private final static String KEY_CURRENT_LOCALE = "current_locale"; private final static String KEY_CURRENT_RENDERING_ELEMENT = "current_rendering_element"; private final static ThreadLocal<Context> instanceHolder = new ThreadLocal<>(); private final static ContextMap globalMap = DelegatedContextMap.createBySingletonConcurrentHashMap(); // this map is not thought to be used in multi threads since the instance of // Context is thread single. private Map<String, ContextMap> scopeMap = new HashMap<>(); // private List @SuppressWarnings("unchecked") public final static <T extends Context> T getCurrentThreadContext() { return (T) instanceHolder.get(); } public final static void setCurrentThreadContext(Context context) { instanceHolder.set(context); } public void setCurrentRenderingElement(Element elem) { setData(KEY_CURRENT_RENDERING_ELEMENT, elem); } public Element getCurrentRenderingElement() { return getData(KEY_CURRENT_RENDERING_ELEMENT); } public void setData(String key, Object data) { setData(SCOPE_DEFAULT, key, data); } public void setData(String scope, String key, Object data) { ContextMap dataMap = acquireMapForScope(scope); dataMap.put(key, data); } public <T> T getData(String key) { return getData(SCOPE_DEFAULT, key); } @SuppressWarnings("unchecked") public <T> T getData(String scope, String key) { if (scope.equals(SCOPE_ATTR)) { return (T) retrieveElementAttr(key); } else { ContextMap dataMap = acquireMapForScope(scope); return (T) dataMap.get(key); } } public <T> ContextDataHolder<T> getDataHolder(String key) { return getDataHolder(SCOPE_DEFAULT, key); } @SuppressWarnings("unchecked") public <T> ContextDataHolder<T> getDataHolder(String scope, String key) { Object v = getData(scope, key); if (v == null) { return null; } else { ContextDataHolder<T> holder = new ContextDataHolder<T>(key, scope, (T) v); return holder; } } public Locale getCurrentLocale() { Locale currentLocale = getData(KEY_CURRENT_LOCALE); if (currentLocale != null) { return currentLocale; } return Locale.getDefault(); } public void setCurrentLocale(Locale locale) { setData(KEY_CURRENT_LOCALE, locale); } private Object retrieveElementAttr(String key) { String dataRef = ExtNodeConstants.ATTR_DATAREF_PREFIX_WITH_NS + key; Element elem = getCurrentRenderingElement(); Object value = null; while (value == null && elem != null) { // for a faked snippet node, we will just jump over it if (elem.tagName().equals(ExtNodeConstants.SNIPPET_NODE_TAG)) { String type = elem.attr(ExtNodeConstants.SNIPPET_NODE_ATTR_TYPE); if (type.equals(ExtNodeConstants.SNIPPET_NODE_ATTR_TYPE_FAKE)) { elem = elem.parent(); continue; } } if (elem.hasAttr(dataRef)) { String id = elem.attr(dataRef); value = getData(SCOPE_EXT_ATTR, id); } else if (elem.hasAttr(key)) { value = elem.attr(key); } elem = elem.parent(); } return value; } protected final ContextMap acquireMapForScope(String scope) { ContextMap dataMap = scopeMap.get(scope); if (dataMap == null) { dataMap = createMapForScope(scope); if (dataMap == null) { dataMap = DelegatedContextMap.createByNonThreadSafeHashMap(); } scopeMap.put(scope, dataMap); } return dataMap; } /** * sub class can override this method for custom scope map instance * * @param scope * @return */ protected ContextMap createMapForScope(String scope) { ContextMap map = null; switch (scope) { case SCOPE_GLOBAL: map = globalMap; break; case SCOPE_EXT_ATTR: map = DelegatedContextMap.createBySingletonConcurrentHashMap(); break; default: map = DelegatedContextMap.createByNonThreadSafeHashMap(); } return map; } public void init() { clear(); ContextBindData.initConext(this); InjectUtil.initContext(this); SnippetInitializeInterceptor.initContext(this); } public void clear() { scopeMap.clear(); } public Context clone() { Context newCtx = new Context(); copyScopesTo(newCtx); return newCtx; } protected void copyScopesTo(Context newCtx) { Set<Entry<String, ContextMap>> entrys = scopeMap.entrySet(); for (Entry<String, ContextMap> entry : entrys) { newCtx.scopeMap.put(entry.getKey(), entry.getValue().createClone()); } } public final static void with(Context context, Runnable runner) { Context oldContext = Context.getCurrentThreadContext(); try { Context.setCurrentThreadContext(context); runner.run(); } finally { Context.setCurrentThreadContext(oldContext); } } public final static <T> T with(Context context, Callable<T> caller) throws Exception { Context oldContext = Context.getCurrentThreadContext(); try { Context.setCurrentThreadContext(context); return caller.call(); } finally { Context.setCurrentThreadContext(oldContext); } } public void with(String varName, Object varValue, Runnable runner) { Object orinialData = null; try { orinialData = this.getData(varName); this.setData(varName, varValue); runner.run(); } finally { // revive the scene this.setData(varName, orinialData); } } public <T> T with(String varName, Object varValue, Callable<T> caller) throws Exception { Object orinialData = null; try { orinialData = this.getData(varName); this.setData(varName, varValue); return caller.call(); } finally { // revive the scene this.setData(varName, orinialData); } } }