/* * Copyright 2012 Google Inc. * * 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.google.gwt.core.server; import com.google.gwt.core.shared.GWT; import com.google.gwt.core.shared.GWTBridge; import com.google.gwt.i18n.server.GwtLocaleFactoryImpl; import com.google.gwt.i18n.shared.GwtLocale; import com.google.gwt.i18n.shared.GwtLocaleFactory; import com.google.gwt.i18n.shared.Localizable; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Stack; import java.util.logging.Level; import java.util.logging.Logger; /** * Implements GWT.* methods for the server. */ public class ServerGwtBridge extends GWTBridge { /** * Something that knows how to provide an instance of a requested class. */ public interface ClassInstantiator { /** * Create an instance given a base class. The created class may be a * subtype of the requested class. * * @param <T> * @param baseClass * @param properties * @return instance or null if unable to create */ <T> T create(Class<?> baseClass, Properties properties); } /** * Helper class that provides some wrappers for looking up and instantiating * a class. */ public abstract static class ClassInstantiatorBase implements ClassInstantiator { /** * @param <T> * @param clazz * @return class instance or null */ protected <T> T tryCreate(Class<T> clazz) { try { T obj = clazz.newInstance(); return obj; } catch (InstantiationException e) { } catch (IllegalAccessException e) { } return null; } /** * @param <T> * @param className * @return class instance or null */ protected <T> T tryCreate(String className) { try { Class<?> clazz = Class.forName(className); @SuppressWarnings("unchecked") T obj = (T) tryCreate(clazz); return obj; } catch (ClassNotFoundException e) { } return null; } } /** * An interface for accessing property values. */ public interface Properties { /** * Get the value of a property. * * @param name * @return property value, or null */ String getProperty(String name); } /** * A node in the tree of registered classes, keeping track of class * instantiators for each type. {@link Object} is at the root of the * tree, and children are not related to each other but inherit from * their parent. */ private static class Node { public final Class<?> type; public final ArrayList<Node> children; public final ArrayList<ClassInstantiator> instantiators; public Node(Class<?> type) { this.type = type; children = new ArrayList<Node>(); instantiators = new ArrayList<ClassInstantiator>(); } } private static class PropertiesImpl implements Properties { private final Object lock = new Object[0]; private final Map<String, String> map = new HashMap<String, String>(); @Override public String getProperty(String name) { synchronized (lock) { return map.get(name); } } public void setProperty(String name, String value) { synchronized (lock) { map.put(name, value); } } } /** * Lookup a property first in thread-local properties, then in global * properties. */ private class PropertyLookup implements Properties { @Override public String getProperty(String name) { String val = threadProperties.get().getProperty(name); if (val == null) { val = globalProperties.getProperty(name); } return val; } } private static Object instanceLock = new Object[0]; private static ServerGwtBridge instance = null; private static final GwtLocaleFactory factory = new GwtLocaleFactoryImpl(); private static final Logger LOGGER = Logger.getLogger(ServerGwtBridge.class.getName()); /** * Get the singleton {@link ServerGwtBridge} instance, creating it if * necessary. The instance will be registered via * {@link GWT#setBridge(GWTBridge)} and will have the default instantiators * registered on it. * * @return the singleton {@link ServerGwtBridge} instance */ public static ServerGwtBridge getInstance() { synchronized (instanceLock) { if (instance == null) { instance = new ServerGwtBridge(); GWT.setBridge(instance); } return instance; } } public static GwtLocale getLocale(Properties properties) { String propVal = properties.getProperty("locale"); if (propVal == null) { propVal = "default"; } return factory.fromString(propVal); } /** * Root of the tree of registered classes and their instantiators. */ private final Node root = new Node(Object.class); // lock for instantiators private final Object instantiatorsLock = new Object[0]; private final ThreadLocal<PropertiesImpl> threadProperties; private final PropertiesImpl globalProperties = new PropertiesImpl(); private Properties properties = new PropertyLookup(); // @VisibleForTesting ServerGwtBridge() { threadProperties = new ThreadLocal<PropertiesImpl>() { @Override protected PropertiesImpl initialValue() { return new PropertiesImpl(); } }; // register built-in instantiators register(Object.class, new ObjectNew()); register(Localizable.class, new LocalizableInstantiator()); } @Override public <T> T create(Class<?> classLiteral) { synchronized (instantiatorsLock) { // Start at the root, and find the bottom-most node that our type // is assignable to. Stack<Node> stack = new Stack<Node>(); stack.push(root); boolean found; do { found = false; Node node = stack.peek(); for (Node child : node.children) { if (child.type.isAssignableFrom(classLiteral)) { found = true; stack.push(child); break; } } } while (found); // Try each instantiator until we find one that can create the // type, walking up the tree. while (!stack.isEmpty()) { Node node = stack.pop(); for (ClassInstantiator inst : node.instantiators) { T obj = inst.<T>create(classLiteral, properties); if (obj != null) { return obj; } } } throw new RuntimeException("No instantiator created " + classLiteral.getCanonicalName()); } } /** * Get the value of the named property, preferring a value specific to this * thread (see {@link #setThreadProperty(String, String)}) over one that is * set globally (see {@link #setGlobalProperty(String, String)}). * * @param property * @return the property's value or null if none */ public String getProperty(String property) { return properties.getProperty(property); } @Override public String getVersion() { return "unknown"; } @Override public boolean isClient() { return false; } @Override public void log(String message, Throwable e) { LOGGER.log(Level.INFO, message, e); } /** * Register an instantiator to be used for any subtypes of a given base class. * * @param baseClass * @param instantiator */ public void register(Class<?> baseClass, ClassInstantiator instantiator) { synchronized (instantiatorsLock) { // find the deepest node which is baseClass or a supertype Node node = root; boolean found; do { found = false; for (Node child : node.children) { if (child.type.isAssignableFrom(baseClass)) { found = true; node = child; } } } while (found); // add the instantiator to the found node if it is an exact match, or // create a new one if not and insert it in the proper place Node nodeToAdd = node; if (node.type != baseClass) { nodeToAdd = new Node(baseClass); // check if this node's children extend baseClass, if so we need // to insert a new node between them boolean needsAdd = true; for (Node child : node.children) { if (baseClass.isAssignableFrom(child.type)) { nodeToAdd.children.add(child); int childPosition = node.children.indexOf(child); node.children.set(childPosition, nodeToAdd); needsAdd = false; break; } } if (needsAdd) { node.children.add(nodeToAdd); } } nodeToAdd.instantiators.add(0, instantiator); } } /** * Set a property value globally. This value will be overridden by any * thread-specific property value of the same name. * * @param property * @param value */ public void setGlobalProperty(String property, String value) { globalProperties.setProperty(property, value); } /** * Set a property value for only the current thread. This value will override * any global property value of the same name. * * @param property * @param value */ public void setThreadProperty(String property, String value) { threadProperties.get().setProperty(property, value); } }