/*
* Copyright (C) 2000 - 2011 TagServlet Ltd
*
* This file is part of Open BlueDragon (OpenBD) CFML Server Engine.
*
* OpenBD is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* Free Software Foundation,version 3.
*
* OpenBD is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenBD. If not, see http://www.gnu.org/licenses/
*
* Additional permission under GNU GPL version 3 section 7
*
* If you modify this Program, or any covered work, by linking or combining
* it with any of the JARS listed in the README.txt (or a modified version of
* (that library), containing parts covered by the terms of that JAR, the
* licensors of this Program grant you additional permission to convey the
* resulting work.
* README.txt @ http://openbd.org/license/README.txt
*
* http://openbd.org/
* $Id: cfINVOKECommon.java 1831 2011-11-27 16:28:49Z alan $
*/
package com.naryx.tagfusion.cfm.tag;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import com.naryx.tagfusion.cfm.engine.catchDataFactory;
import com.naryx.tagfusion.cfm.engine.cfArgStructData;
import com.naryx.tagfusion.cfm.engine.cfBooleanData;
import com.naryx.tagfusion.cfm.engine.cfCatchData;
import com.naryx.tagfusion.cfm.engine.cfComponentData;
import com.naryx.tagfusion.cfm.engine.cfData;
import com.naryx.tagfusion.cfm.engine.cfJavaObjectData;
import com.naryx.tagfusion.cfm.engine.cfSession;
import com.naryx.tagfusion.cfm.engine.cfStringData;
import com.naryx.tagfusion.cfm.engine.cfStructData;
import com.naryx.tagfusion.cfm.engine.cfWSParameters;
import com.naryx.tagfusion.cfm.engine.cfcMethodData;
import com.naryx.tagfusion.cfm.engine.cfmBadFileException;
import com.naryx.tagfusion.cfm.engine.cfmRunTimeException;
import com.naryx.tagfusion.cfm.parser.CFUndefinedValue;
import com.naryx.tagfusion.cfm.parser.script.userDefinedFunction;
public abstract class cfINVOKECommon extends cfTag implements cfOptionalBodyTag, Serializable {
static final long serialVersionUID = 1;
public static final String DATA_BIN_KEY = "CFINVOKE_DATA";
protected String endMarker = null;
protected boolean componentCall = false;
public cfINVOKECommon() {
endMarker = null;
tagName = "CFINVOKE";
}
public String getEndMarker() {
return endMarker;
}
public void setEndTag() {
endMarker = null;
}
public void lookAheadForEndTag(tagReader inFile) {
endMarker = (new tagLocator(tagName, inFile)).findEndMarker();
}
protected void defaultParameters(String _tag) throws cfmBadFileException {
parseTagHeader(_tag);
if (containsAttribute("ATTRIBUTECOLLECTION"))
return;
// CFMX doesn't require a method attribute with CFINVOKE, but
// CFINVOKE provides no useful purpose without it
if (!containsAttribute("METHOD"))
throw missingAttributeException("CFINVOKE tag must contain a METHOD attribute", null);
}
public cfTagReturnType render(cfSession _Session) throws cfmRunTimeException {
cfStructData attributes = setAttributeCollection(_Session);
if (containsAttribute(attributes, "WEBSERVICE")) {
this.componentCall = false;
renderWebService(attributes, _Session);
} else if (containsAttribute(attributes, "COMPONENT")) {
this.componentCall = true;
renderComponent(attributes, _Session);
} else {
this.componentCall = true;
renderComponentMethod(attributes, _Session);
}
return cfTagReturnType.NORMAL;
}
protected abstract void renderWebService(cfStructData attributes, cfSession session) throws cfmRunTimeException;
protected abstract cfData invokeJavaMethod(cfStructData attributes, cfSession _Session, Object theObj) throws cfmRunTimeException;
private void renderComponent(cfStructData attributes, cfSession session) throws cfmRunTimeException {
cfData rtn = null;
cfComponentData component = null;
if (this.containsAttribute(attributes,"COMPONENT")) {
String methodName = getDynamic(attributes,session, "METHOD").getString();
cfData comp = getDynamic(attributes,session, "COMPONENT");
if (comp.getDataType() == cfData.CFSTRINGDATA) {
// not a component instance so create one of specified type
// in CFMX this could be a java class, but we aren't supporting that
component = new cfComponentData(session, comp.getString());
rtn = invokeComponentMethod(attributes, session, component, methodName);
} else if (comp.getDataType() == cfData.CFCOMPONENTOBJECTDATA) {
// must be a component instance
component = (cfComponentData) comp;
rtn = invokeComponentMethod(attributes, session, component, methodName);
} else if (comp.getDataType() == cfData.CFJAVAOBJECTDATA) {
this.componentCall = false;
cfJavaObjectData obj = (cfJavaObjectData) comp;
rtn = invokeJavaMethod(attributes, session, obj.getInstance());
} else {
throw newRunTimeException(catchDataFactory.invalidTagAttributeException(this, "COMPONENT attribute contains unsupported type"));
}
} else {
rtn = this.renderComponentMethod(attributes, session);
}
if (containsAttribute(attributes, "RETURNVARIABLE")) {
cfStringData returnVariableName = (cfStringData) getDynamic(attributes, session, "RETURNVARIABLE");
if (returnVariableName.getLength() != 0) {
session.setData(returnVariableName.toString(), rtn);
}
}
}
private cfData renderComponentMethod(cfStructData attributes, cfSession session) throws cfmRunTimeException {
cfData rtn = null;
String methodName = getDynamic(attributes, session, "METHOD").getString();
cfComponentData component = session.getActiveComponentData();
if (component != null)
rtn = invokeComponentMethod(attributes, session, component, methodName);
else
rtn = invokeUDF(attributes, session, methodName);
if (containsAttribute(attributes, "RETURNVARIABLE")) {
cfStringData returnVariableName = (cfStringData) getDynamic(attributes, session, "RETURNVARIABLE");
if (returnVariableName.getLength() > 0) {
session.setData(returnVariableName.toString(), rtn);
}
}
return rtn;
}
private cfData invokeComponentMethod(cfStructData attributes, cfSession session, cfComponentData component, String methodName) throws cfmRunTimeException {
cfcMethodData invocationData = new cfcMethodData(session, methodName, getNamedArguments(attributes, session));
return component.invokeComponentFunction(session, invocationData);
}
private cfData invokeUDF(cfStructData attributes, cfSession session, String functionName) throws cfmRunTimeException {
cfData udf = session.getData(functionName);
if ((udf == null) || !(udf instanceof userDefinedFunction))
throw newRunTimeException(catchDataFactory.tagRuntimeException(this, "Could not find function: " + functionName, null));
return ((userDefinedFunction) udf).execute(session, getNamedArguments(attributes, session), false);
}
private cfArgStructData getNamedArguments(cfStructData attributes, cfSession session) throws cfmRunTimeException {
cfWSParameters args = getArguments(attributes, session, null);
cfArgStructData namedArguments = new cfArgStructData();
String[] argNames = args.getNamesArray();
Object[] argValues = args.getValuesArray();
for (int i = 0; i < argNames.length; i++)
namedArguments.setData(argNames[i], (cfData) argValues[i]);
return namedArguments;
}
// CFMX's order of argument/attribute precedence is:
// 1. Values collected from subordinate CFINVOKEARGUMENT tags
// 2. Values collected from additional attributes to the CFINVOKE tag
// 3. Values collected from the the structure passed in the ARGUMENTCOLLECTION attribute
// Note: The values specified in subordinate CFINVOKEARGUEMENT tags and
// the values specified in the ARGUMENTCOLLECTION struct are not subject
// to the filtering of reservedWords. All values specified there are to
// be passed directly to the target web service itself.
protected cfWSParameters getArguments(cfStructData attributes, cfSession _Session, String[] reservedWords) throws cfmRunTimeException {
cfWSParameters op = new cfWSParameters(reservedWords);
cfData key = null;
cfData val = null;
Object naturalVal = null;
// Get any arguments from the ARGUMENTCOLLECTION ref
if (containsAttribute(attributes, "ARGUMENTCOLLECTION")) {
cfData argsCol = getDynamic(attributes, _Session, "ARGUMENTCOLLECTION");
processArgumentCollection(_Session, op, key, argsCol, false, false);
}
// Get any arguments passed in as extra attributes
String[] reserved = new String[] { "ARGUMENTCOLLECTION", "COMPONENT", "METHOD", "RETURNVARIABLE", "WEBSERVICE" };
Iterator<String> iter = this.properties.keySet().iterator();
while (iter.hasNext()) {
String k = iter.next();
boolean isReserved = false;
for (int i = 0; i < reserved.length; i++) {
if (reserved[i].equalsIgnoreCase(k)) {
isReserved = true;
break;
}
}
if (!isReserved) {
val = getDynamic(_Session, k);
naturalVal = getNatural(_Session, val, k);
op.add(k, naturalVal, true, false);
}
}
// Get any arguments passed in on nested CFINVOKEARGUMENT tags
Map<cfData, cfData[]> invokeArgs = new HashMap<cfData, cfData[]>();
// save old arguments for nesting of CFINVOKE tags; see bug #115
Object oldInvokeArgs = _Session.getDataBin(DATA_BIN_KEY);
_Session.setDataBin(DATA_BIN_KEY, invokeArgs);
renderToString(_Session);
Iterator<cfData> argsIter = invokeArgs.keySet().iterator();
while (argsIter.hasNext()) {
key = argsIter.next();
if (!cfStringData.class.isAssignableFrom(key.getClass())) {
throw newRunTimeException(catchDataFactory.invalidTagAttributeException(this, "NAME"));
}
cfData[] vals = invokeArgs.get(key);
val = vals[0];
if (!cfJavaObjectData.class.isAssignableFrom(val.getClass()) && (val.getDataType() != cfData.CFNULLDATA)) {
throw newRunTimeException(catchDataFactory.invalidTagAttributeException(this, "VALUE"));
}
if (key.getString().equalsIgnoreCase("ARGUMENTCOLLECTION")) {
processArgumentCollection(_Session, op, key, val, false, ((cfBooleanData) vals[1]).getBoolean());
} else {
String k = key.toString();
naturalVal = getNatural(_Session, val, k);
op.add(k, naturalVal, false, ((cfBooleanData) vals[1]).getBoolean());
}
}
if (oldInvokeArgs == null) {
_Session.deleteDataBin(DATA_BIN_KEY);
} else { // restore old arguments for nesting of CFINVOKE tags; see bug #115
_Session.setDataBin(DATA_BIN_KEY, oldInvokeArgs);
}
// Return the arguments
return op;
}
protected Object getNatural(cfSession _Session, cfData value, String attributeName) {
Object naturalVal;
if (!this.componentCall)
naturalVal = tagUtils.getNatural(value, true, true);
else
naturalVal = value;
if (naturalVal == null) {
cfCatchData catchData = new cfCatchData(_Session);
catchData.setType(cfCatchData.TYPE_TEMPLATE);
catchData.setErrorCode("errorCode.runtimeError");
catchData.setDetail("Attribute " + attributeName + " has an invalid value");
catchData.setMessage("Invalid web service parameter.");
return new cfmRunTimeException(catchData);
}
return naturalVal;
}
protected void processArgumentCollection(cfSession _Session, cfWSParameters op, cfData key, cfData argsCol, boolean filter, boolean omit) throws cfmRunTimeException {
if (argsCol == null || !cfStructData.class.isAssignableFrom(argsCol.getClass())) {
throw newRunTimeException("Attribute ARGUMENTCOLLECTION must be a Struct");
}
cfStructData aa = (cfStructData) argsCol;
Iterator itr = aa.keySet().iterator();
String strKey = null;
while (itr.hasNext()) {
Object obj = itr.next();
if (obj instanceof String) {
strKey = (String) obj;
} else if (obj instanceof cfStringData) {
strKey = ((cfStringData) obj).toString();
} else if (!cfStringData.class.isAssignableFrom(key.getClass())) {
throw newRunTimeException("ARGUMENTCOLLECTION Struct contains invalid key");
}
cfData val = aa.getData(strKey);
if ( val instanceof CFUndefinedValue )
continue;
else if (!cfJavaObjectData.class.isAssignableFrom(val.getClass())) {
throw newRunTimeException("ARGUMENTCOLLECTION Struct contains invalid value");
}
Object naturalVal = getNatural(_Session, val, strKey);
op.add(strKey, naturalVal, filter, omit);
}
}
}