blob: 52a875ecdd7370d25bd99ad593b0b58c68df842c [file] [log] [blame]
package org.apache.velocity.tools;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
import java.util.HashMap;
import java.util.Map;
import org.apache.velocity.tools.config.Data;
import org.apache.velocity.tools.config.FactoryConfiguration;
import org.apache.velocity.tools.Scope;
import org.apache.velocity.tools.config.ToolboxConfiguration;
import org.apache.velocity.tools.config.ToolConfiguration;
/**
* <p>This class is the central point of action for VelocityTools.
* It manages the configured and scoped {@link ToolInfo} and {@link Data}
* and is meant to stick around for the life of the application.
* </p><p>
* It works like this:
* </p>
* <ol>
* <li>Build up your {@link FactoryConfiguration}(s)</li>
* <li>Create a {@link ToolboxFactory} instance</li>
* <li>Pass the configuration to {@link #configure}</li>
* <li>When appropriate for each scope, use {@link #createToolbox}
* to create the {@link Toolbox} for that scope and put that toolbox
* somewhere appropriate to that scope.</li>
* <li>When you want a tool, get that {@link Toolbox} and
* ask it for the tool you want (e.g. <code>toolbox.get("math")</code>).</li>
* </ol>
* <p>
* Of course, most users will not have to do any of this
* as much of it is handled for them by some combination of
* {@link ToolManager} or org.apache.velocity.tools.view.VelocityView
* and a {@link ToolContext} or org.apache.velocity.tools.view.ViewToolContext.
* </p><p>
* <strong>NOTE:</strong> While you are free to pass in new configuration info
* at any time, that data will only affect {@link Toolbox}es created subsequently.
* Any previously created toolboxes will have to be re-created and replaced to
* reflect the changes to the configuration.
* </p>
*
* @author Nathan Bubna
* @version $Id: ToolboxFactory.java 511959 2007-02-26 19:24:39Z nbubna $
*/
public class ToolboxFactory
{
public static final String DEFAULT_SCOPE = Scope.REQUEST;
private final Map<String,Map<String,ToolInfo>> scopedToolInfo;
private final Map<String,Map<String,Object>> scopedProperties;
private Map<String,Object> data;
private Map<String,Object> globalProperties;
public ToolboxFactory()
{
this.scopedToolInfo = new HashMap<String,Map<String,ToolInfo>>();
this.scopedProperties = new HashMap<String,Map<String,Object>>();
}
public synchronized void configure(FactoryConfiguration config)
{
// this will throw a ConfigurationException if there is a problem
config.validate();
// first do the easy part and add any data
for (Data datum : config.getData())
{
putData(datum.getKey(), datum.getConvertedValue());
}
// property precedence follows two rules:
// newer Foo-level props beat older ones
// narrower-scoped props beat broader-scoped ones
// next add the toolboxes
for (ToolboxConfiguration toolbox : config.getToolboxes())
{
String scope = toolbox.getScope();
// starting with the toolinfo
for (ToolConfiguration tool : toolbox.getTools())
{
addToolInfo(scope, tool.createInfo());
}
// then add the properties for this toolbox
Map<String,Object> newToolboxProps = toolbox.getPropertyMap();
putProperties(scope, newToolboxProps);
// now go thru all toolinfo for this scope old and new
for (ToolInfo info : getToolInfo(scope).values())
{
// and add these new toolbox properties, which have
// lower precedence than the props already in the info
info.addProperties(newToolboxProps);
}
}
// now set all the factory-level properties
// new ones will override old ones
Map<String,Object> newGlobalProps = config.getPropertyMap();
putGlobalProperties(newGlobalProps);
// now go thru all toolboxes in this factory
for (Map<String,ToolInfo> toolbox : scopedToolInfo.values())
{
// iterating over all the toolinfo in them
for (ToolInfo info : toolbox.values())
{
// and adding the new global properties last
// since they have the lowest precedence
info.addProperties(newGlobalProps);
}
}
}
protected synchronized Object putData(String key, Object value)
{
if (data == null)
{
data = new HashMap<String,Object>();
}
return data.put(key, value);
}
protected void addToolInfo(String scope, ToolInfo tool)
{
//TODO? check the scope against any "ValidScopes"
// annotation on the tool class, or do we leave
// validation like this to FactoryConfiguration?
getToolInfo(scope).put(tool.getKey(), tool);
}
protected synchronized Map<String,ToolInfo> getToolInfo(String scope)
{
Map<String,ToolInfo> tools = scopedToolInfo.get(scope);
if (tools == null)
{
tools = new HashMap<String,ToolInfo>();
scopedToolInfo.put(scope, tools);
}
return tools;
}
protected synchronized void putGlobalProperties(Map<String,Object> props)
{
if (props != null && !props.isEmpty())
{
if (globalProperties == null)
{
globalProperties = new HashMap<String,Object>(props);
}
else
{
globalProperties.putAll(props);
}
}
}
protected synchronized void putProperties(String scope, Map<String,Object> props)
{
if (props != null && !props.isEmpty())
{
Map<String,Object> properties = scopedProperties.get(scope);
if (properties == null)
{
properties = new HashMap<String,Object>(props);
scopedProperties.put(scope, properties);
}
else
{
properties.putAll(props);
}
}
}
public Object getGlobalProperty(String name)
{
if (globalProperties == null)
{
return null;
}
return globalProperties.get(name);
}
public Map<String,Object> getData()
{
return data;
}
public boolean hasTools(String scope)
{
Map<String,ToolInfo> tools = scopedToolInfo.get(scope);
if (tools != null && !tools.isEmpty())
{
return true;
}
else if (data != null && Scope.APPLICATION.equals(scope))
{
return true;
}
return false;
}
public Toolbox createToolbox(String scope)
{
Map<String,ToolInfo> tools = scopedToolInfo.get(scope);
Map properties = scopedProperties.get(scope);
Toolbox toolbox;
if (properties == null)
{
if (globalProperties == null)
{
toolbox = new Toolbox(tools);
}
else
{
toolbox = new Toolbox(tools, globalProperties);
}
}
else
{
//TODO: this will waste cycles on subsequent retrievals
// of the same toolbox. consider improving...
if (globalProperties != null)
{
properties.putAll(globalProperties);
}
toolbox = new Toolbox(tools, properties);
}
// if application scoped or if there's only one toolbox,
// then automatically include data, if we have any.
if (data != null &&
(scopedToolInfo.size() == 1 || scope.equals(Scope.APPLICATION)))
{
toolbox.cacheData(getData());
}
return toolbox;
}
}