/**
* Copyright 2011 meltmedia
*
* 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 org.xchain.framework.jxpath;
import javax.xml.namespace.QName;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.jxpath.JXPathContext;
import org.xchain.annotations.Begin;
import org.xchain.annotations.End;
import org.xchain.annotations.In;
import org.xchain.annotations.PrefixMapping;
import org.xchain.framework.lifecycle.ComponentAnalysis;
import org.xchain.framework.lifecycle.Lifecycle;
import org.xchain.framework.lifecycle.LifecycleContext;
import org.xchain.framework.lifecycle.NamespaceContext;
import org.xchain.framework.util.ComponentUtil;
import org.xchain.framework.util.DependencyInjectionException;
import static org.xchain.framework.util.JXPathContextUtil.*;
/**
* An implementation of the ScopedQNameVariables interface.
*
* @author Christian Trimble
* @author Devon Tackett
*/
public class ScopedQNameVariablesImpl
extends QNameVariablesImpl
implements ScopedQNameVariables
{
protected ScopedQNameVariables parentVariables = null;
protected Map<QName, Object> componentMap = new HashMap<QName, Object>();
protected Scope scope = null;
/**
* Creates a new QName variables object that does not have a context assigned for qname lookups.
*/
public ScopedQNameVariablesImpl( ScopedQNameVariables parentVariables, Scope scope)
{
this.parentVariables = parentVariables;
this.scope = scope;
}
/**
* Creates a new QName variables object that has a context assigned for qname lookups.
*/
public ScopedQNameVariablesImpl( JXPathContext context, ScopedQNameVariables parentVariables, Scope scope )
{
super( context );
this.parentVariables = parentVariables;
this.scope = scope;
}
/**
* Creates a new QName variables object that shares its variable map with another qname variables object.
*/
protected ScopedQNameVariablesImpl( JXPathContext context, Map variableMap, ScopedQNameVariables parentVariables, Scope scope )
{
super( context, variableMap );
this.parentVariables = parentVariables;
this.scope = scope;
}
public void declareVariable( String qName, Object value )
{
declareVariable( stringToQName(context,qName), value );
}
public void declareVariable(QName varName, Object value) {
if (getComponentAnalysis(varName) != null)
throw new IllegalStateException("Unable to declare the variable " + varName + " as it conflicts with a registered component.");
super.declareVariable(varName, value);
}
public void declareVariable( String varName, Object value, Scope scope )
{
declareVariable( stringToQName(context,varName), value, scope );
}
public void declareVariable( QName varName, Object value, Scope scope )
{
if (this.scope == scope) {
declareVariable(varName, value);
} else if (parentVariables != null) {
parentVariables.declareVariable(varName, value, scope);
} else {
throw new IllegalStateException("Unable to access scope " + scope);
}
}
public Object getVariable( String qName )
{
return getVariable( stringToQName(context, qName) );
}
public Object getVariable(QName varName) {
Object variable = null;
// Check if a component analysis exists for the QName
ComponentAnalysis componentAnalysis = getComponentAnalysis(varName);
if (componentAnalysis != null) {
// Check if the component is for the current scope
if (componentAnalysis.getScope() != scope) {
if (parentVariables != null) {
// Check the parent variables
variable = parentVariables.getVariable(varName);
} else {
// No parent variables and the component is not for this scope. Throw an exception.
throw new IllegalStateException("Unable to access component for scope " + componentAnalysis.getScope());
}
} else {
// Get the component at the current scope.
variable = getComponent(varName);
}
}
if (variable == null)
variable = super.getVariable(varName);
return variable;
}
public Object getVariable( String qName, Scope scope )
{
return getVariable( stringToQName(context, qName), scope );
}
public Object getVariable( QName qName, Scope scope )
{
if (this.scope == scope) {
return getVariable(qName);
} else if (parentVariables != null) {
return parentVariables.getVariable(qName, scope);
} else {
throw new IllegalStateException("Unable to access scope " + scope);
}
}
public boolean isDeclaredVariable( String varName )
{
return isDeclaredVariable( stringToQName(context, varName) );
}
public boolean isDeclaredVariable(QName varName) {
// Consider the variable declared if a component exists for the QName at this scope.
ComponentAnalysis componentAnalysis = getComponentAnalysis(varName);
if (componentAnalysis != null && componentAnalysis.getScope() == scope)
return true;
return super.isDeclaredVariable(varName);
}
/**
* Determine if a variable with the given name is declared at the given scope.
*
* @param varName The name of the variable.
* @param scope The scope to search on.
*
* @return True if the variable is declared. False if it is not.
*/
public boolean isDeclaredVariable( String varName, Scope scope )
{
return isDeclaredVariable( stringToQName(context,varName), scope );
}
public boolean isDeclaredVariable( QName varName, Scope scope )
{
if (this.scope == scope) {
return isDeclaredVariable(varName);
} else if (parentVariables != null) {
return parentVariables.isDeclaredVariable(varName, scope);
} else {
throw new IllegalStateException("Unable to access scope " + scope);
}
}
public void undeclareVariable( String varName )
{
undeclareVariable( stringToQName(context,varName) );
}
public void undeclareVariable( String varName, Scope scope )
{
undeclareVariable( stringToQName(context,varName), scope );
}
public void undeclareVariable( QName varName, Scope scope )
{
if (this.scope == scope) {
undeclareVariable(varName);
} else if (parentVariables != null) {
parentVariables.undeclareVariable(varName, scope);
} else {
throw new IllegalStateException("Unable to access scope " + scope);
}
}
/**
* Release all components that were declared. Any methods annotated with End will be called on the Compoent instance.
*/
public void releaseComponents() {
for (Object component : componentMap.values()) {
try {
// Run the end method (if present)
ComponentUtil.doEnd(component);
} catch (Exception ex) {
if (log.isErrorEnabled()) {
log.error("Error releasing component.", ex);
}
}
}
// Clear out the component map
componentMap.clear();
}
/**
* Get an instance of a component with the given QName.
*
* @param componentName The QName of the component to load.
*
* @return An instance of the component for the given QName. Null if there is no component
* for the given QName.
*/
private Object getComponent(QName componentName) {
if (componentMap.containsKey(componentName)) {
// Component already instantiated.
return componentMap.get(componentName);
} else {
Object component = null;
// Attempt to find the component class
ComponentAnalysis analysis = getComponentAnalysis(componentName);
if (analysis != null) {
try {
// Create the component
component = ComponentUtil.createComponent(analysis);
// Perform dependency injection
ComponentUtil.doInjection(component, analysis, context);
// Run the begin method (if present)
ComponentUtil.doBegin(component);
// Add the component to the component map.
componentMap.put(componentName, component);
} catch (DependencyInjectionException ex) {
throw ex;
} catch (Exception ex) {
if (log.isErrorEnabled()) {
log.error("Error creating component: " + componentName, ex);
}
throw new IllegalArgumentException("Error creating component: " + componentName, ex);
}
}
return component;
}
}
/**
* Get the component class for the given QName.
*
* @param componentName
*
* @return The component class for the given QName. Null if the QName does not reference a known component.
*/
private ComponentAnalysis getComponentAnalysis(QName componentName) {
NamespaceContext namespaceContext = null;
// Get the lifecycle context
LifecycleContext lifecycleContext = Lifecycle.getLifecycleContext();
if (lifecycleContext != null) {
// Get the namespace context.
namespaceContext = lifecycleContext.getNamespaceContextMap().get(componentName.getNamespaceURI());
}
if (namespaceContext != null) {
// Get the component from the namespace context.
return namespaceContext.getComponentMap().get(componentName.getLocalPart());
} else {
// Namespace is not defined. No component could exist.
return null;
}
}
@Override
protected void finalize() throws Throwable {
if (componentMap.size() != 0) {
if (log.isWarnEnabled()) {
log.warn("Components at scope: " + scope + " were not properly released.");
}
releaseComponents();
}
}
}