/*
* RHQ Management Platform
* Copyright (C) 2005-2008 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/**
* Created on Mar 16, 2003
*/
package org.rhq.enterprise.gui.legacy.taglib;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.TagSupport;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.taglibs.standard.tag.common.core.Util;
/**
* A sensible way to handle constants in a webapp is to define a bean that holds all of the constants as attributes.
* However, Java programmers have a propensity for creating constants as classes that have the values defined static
* final members. This tag exposes these attribute handles to save the developer from having to read the constants class
* source code to determine the returned values. This way the JSP source and the backend bean (and/or bean handlers)
* have a consistent interface to the constants class.
*
* <p/>Under the hood, the tag uses reflection to build a map of names and values. The map is cached to save the expense
* of repeated runtime reflection.
*
* <p/>Usage: Suppose you have a class com.example.Constants:
*
* <p/>public class Constants { public static final int WARMRESTART = 0; public static final int COLDRESTART = 1; public
* static final int HARDRESTART = 2; public static final String RO = "rock"; public static final String SHAM = "paper";
* public static final String BO = "scissors"; }
*
* <p/>and you want to uniformly access the symbold names in the jsp as you would in your Java classes. Use this tag.
*
* <p/><%@ taglib uri="/WEB-INF/constants.tld" prefix="constants" %> ... someplace where a constant needs to be accessed:
* <constants:constant symbol="WARMRESTART" /> Important: This usage assumes the class to access is specified in a
* web-xml/context-param
*
* <p/>The alternate form <constants:constant classname="com.example.Constants" symbol="WARMRESTART" /> can be used to
* accomadate having multiple constants classes.
*
* <p/>Another usage is set attributes, within an optionally specified scope <constants:constant symbol="WARMRESTART"
* var="restartType" scope="session" /> or just <constants:constant symbol="WARMRESTART" var="restartType" /> to put it
* in the page scope. Either way, at sometime later, you can do this <c:out value="${restartType}" />
*
* @author Hyperic
* @author Ian Springer (added support for Enums)
*/
public class ConstantsTag extends TagSupport {
private static Log log = LogFactory.getLog(ConstantsTag.class.getName());
// This tag might handle multiple constants classes, in which
// case we'll store them as a map. The keys are the class
// names, the values are maps of constant names/values.
protected static Map constants = new HashMap();
public static final String constantsClassNameParam = "context-constants";
// optional
// required iff "context-constants" is not a config'd
// param for the webapp context
private String className = null;
public void setClassname(String aClass) {
className = aClass;
}
public String getClassname() {
return className;
}
private String var = null;
private boolean varSpecified = false;
public void setVar(String aVar) {
var = aVar;
varSpecified = true;
}
public String getVar() {
return var;
}
// if you want the page to fail if not configured, set this to true
// by default, always fail if the Constant is not found. - tmk
private boolean failmode = true;
public void setFailmode(boolean theFailmode) {
failmode = theFailmode;
}
public boolean getFailmode() {
return failmode;
}
// required
private String symbol = null;
public void setSymbol(String aSymbol) {
symbol = aSymbol;
}
public String getSymbol() {
return symbol;
}
private String scopeName;
private int scope = PageContext.PAGE_SCOPE;
private boolean scopeSpecified = false;
public void setScope(String aScopeName) {
scopeName = aScopeName;
scope = Util.getScope(aScopeName);
scopeSpecified = true;
}
public String getScope() {
return scopeName;
}
public void release() {
super.release();
className = symbol = var = scopeName = null;
failmode = false;
failmode = scopeSpecified = false;
scope = PageContext.PAGE_SCOPE;
}
public int doEndTag() throws JspException {
try {
JspWriter out = pageContext.getOut();
if (className == null) {
className = pageContext.getServletContext().getInitParameter(constantsClassNameParam);
}
if (validate(out)) {
// we're misconfigured. getting this far
// is a matter of what our failure mode is;
// if we haven't thrown an Error, carry on
log.debug("constants tag misconfigured");
return EVAL_PAGE;
}
Map<String, String> fieldMap;
if (constants.containsKey(className)) {
// we cache the result of the constant's class
// reflection field walk as a map
fieldMap = (Map<String, String>) constants.get(className);
} else {
fieldMap = new HashMap<String, String>();
Class typeClass = Class.forName(className);
if (typeClass.isEnum()) {
for (Object enumConstantObj : typeClass.getEnumConstants()) {
Enum enumConstant = (Enum) enumConstantObj;
// Set name *and* value to enum name (e.g. name of ResourceCategory.PLATFORM = "PLATFORM")
// NOTE: We do not set the value to enumConstant.ordinal(), because there is no way to
// convert the ordinal value back to an Enum (i.e. no Enum.valueOf(int ordinal) method).
fieldMap.put(enumConstant.name(), enumConstant.name());
}
} else {
Object instance = typeClass.newInstance();
Field[] fields = typeClass.getFields();
for (Field field : fields) {
// string comparisons of class names should be cheaper
// than reflective Class comparisons, the asumption here
// is that most constants are Strings, ints and booleans
// but a minimal effort is made to accomadate all types
// and represent them as String's for our tag's output
String fieldType = field.getType().getName();
String strVal;
if (fieldType.equals("java.lang.String")) {
strVal = (String) field.get(instance);
} else if (fieldType.equals("int")) {
strVal = Integer.toString(field.getInt(instance));
} else if (fieldType.equals("boolean")) {
strVal = Boolean.toString(field.getBoolean(instance));
} else if (fieldType.equals("char")) {
strVal = Character.toString(field.getChar(instance));
} else if (fieldType.equals("double")) {
strVal = Double.toString(field.getDouble(instance));
} else if (fieldType.equals("float")) {
strVal = Float.toString(field.getFloat(instance));
} else if (fieldType.equals("long")) {
strVal = Long.toString(field.getLong(instance));
} else if (fieldType.equals("short")) {
strVal = Short.toString(field.getShort(instance));
} else if (fieldType.equals("byte")) {
strVal = Byte.toString(field.getByte(instance));
} else {
strVal = field.get(instance).toString();
}
fieldMap.put(field.getName(), strVal);
}
}
// cache the result
constants.put(className, fieldMap);
}
if ((symbol != null) && !fieldMap.containsKey(symbol)) {
// tell the developer that he's being a dummy and what
// might be done to remedy the situation
// TODO: what happens if the constants change?
// do we need to throw a JspException, here? - mtk
String err1 = symbol + " was not found in " + className + "\n";
String err2 = err1 + "use <constants:diag classname=\"" + className + "\"/>\n"
+ "to figure out what you're looking for";
log.error(err2);
die(out, err1);
}
if (varSpecified) {
doSet(fieldMap);
} else {
doOutput(fieldMap, out);
}
} catch (JspException e) {
throw e;
} catch (Exception e) {
log.debug("doEndTag() failed: ", e);
throw new JspException("Could not access constants tag", e);
}
return EVAL_PAGE;
}
protected void doOutput(Map fieldMap, JspWriter out) throws java.io.IOException {
out.print(fieldMap.get(symbol));
}
protected void doSet(Map fieldMap) {
pageContext.setAttribute(var, fieldMap.get(symbol), scope);
}
/**
* Checks for broken configuration/attribute combinations.
*
* @return true is validation fails
*/
protected boolean validate(JspWriter out) throws JspException {
// look for configuration errors
if (className == null) {
// the tag has to be parameterized either at tag
// invocation time or as a web.xml
// web-app/context-param element (which takes
// param-name and param-value subelements)
// if it's null here, it's busted
die(out);
return true;
}
if ((scopeName != null) && (var == null)) {
// setting the scope but not a var to assign to
// is a misconfiguration
die(out);
return true;
}
return false;
}
protected void die(JspWriter out) throws JspException {
die(out, "");
}
protected void die(JspWriter out, String err) throws JspException {
if (failmode) {
throw new JspException(err);
} else {
try {
out.println("<!-- constants tag misconfigured -->");
} catch (java.io.IOException e) {
// misconfigured and hosed, what a difficult
// situation
log.debug("constants tag misconfigured: ", e);
}
}
}
}