package org.displaytag.tld;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.servlet.jsp.tagext.TagSupport;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import junit.framework.TestCase;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Assert;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
/**
* Reads tlds and check tag classes for declared attributes. This simple reports missing/invalid setters in tag classes.
* Basic tests only, other tests are performed by the maven-taglib plugin.
* @author Fabrizio Giustina
* @version $Revision$ ($Author$)
*/
public class TldTest extends TestCase
{
/**
* logger.
*/
private static Log log = LogFactory.getLog(TldTest.class);
/**
* @see junit.framework.TestCase#getName()
*/
public String getName()
{
return getClass().getName() + "." + super.getName();
}
/**
* Check displaytag 1.2 dtd.
* @throws Exception any Exception generated during test.
*/
public void testStandardTld() throws Exception
{
checkTld("/META-INF/displaytag.tld");
}
/**
* Check the given tld. Assure then:
* <ul>
* <li>Any tag class is loadable</li>
* <li>the tag class has a setter for any of the declared attribute</li>
* <li>the type declared in the dtd for an attribute (if any) matches the type accepted by the getter</li>
* </ul>
* @param checkedTld path for the tld to check, relative to basedir.
* @throws Exception any Exception generated during test.
*/
public void checkTld(String checkedTld) throws Exception
{
List<TagAttribute> tagsAttributes = getTagAttributeList(checkedTld);
List<String> errors = new ArrayList<String>();
Iterator<TagAttribute> iterator = tagsAttributes.iterator();
while (iterator.hasNext())
{
TagAttribute attribute = iterator.next();
if (log.isDebugEnabled())
{
log.debug("testing " + attribute);
}
String className = attribute.getTagClass();
Class tagClass = null;
try
{
tagClass = Class.forName(className);
}
catch (ClassNotFoundException e)
{
errors.add("unable to find declared tag class [" + className + "]");
continue;
}
if (!TagSupport.class.isAssignableFrom(tagClass))
{
errors.add("Declared class [" + className + "] doesn't extend TagSupport");
continue;
}
// load it
Object tagObject = null;
try
{
tagObject = tagClass.newInstance();
}
catch (Throwable e)
{
errors.add("unable to instantiate declared tag class [" + className + "]");
continue;
}
if (!PropertyUtils.isWriteable(tagObject, attribute.getAttributeName()))
{
errors.add("Setter for attribute [" + attribute.getAttributeName() + "] not found in " + className);
continue;
}
Class propertyType = PropertyUtils.getPropertyType(tagObject, attribute.getAttributeName());
String tldType = attribute.getAttributeType();
if (tldType != null)
{
Class tldTypeClass = getClassFromName(tldType);
if (!propertyType.isAssignableFrom(tldTypeClass))
{
errors.add("Tag attribute ["
+ attribute.getAttributeName()
+ "] declared in tld as ["
+ tldType
+ "], class declare ["
+ propertyType.getName()
+ "]");
continue;
}
}
}
if (errors.size() > 0)
{
if (log.isInfoEnabled())
{
log.info(errors.size() + " errors found in tag classes: " + errors);
}
Assert.fail(errors.size() + " errors found in tag classes: " + errors);
}
}
/**
* returns a class from its name, handling primitives.
* @param className clss name
* @return Class istantiated using Class.forName or the matching primitive.
*/
private Class getClassFromName(String className)
{
Class tldTypeClass = null;
if ("int".equals(className))
{
tldTypeClass = int.class;
}
else if ("long".equals(className))
{
tldTypeClass = long.class;
}
else if ("double".equals(className))
{
tldTypeClass = double.class;
}
else if ("boolean".equals(className))
{
tldTypeClass = boolean.class;
}
else if ("char".equals(className))
{
tldTypeClass = char.class;
}
else if ("byte".equals(className))
{
tldTypeClass = byte.class;
}
if (tldTypeClass == null)
{
// not a primitive type
try
{
tldTypeClass = Class.forName(className);
}
catch (ClassNotFoundException e)
{
Assert.fail("unable to find class [" + className + "] declared in 'type' attribute");
}
}
return tldTypeClass;
}
/**
* Extract a list of attributes from tld.
* @param checkedTld path for the checked tld, relative to basedir.
* @return List of TagAttribute
* @throws Exception any Exception thrown during test
*/
private List<TagAttribute> getTagAttributeList(String checkedTld) throws Exception
{
InputStream is = getClass().getResourceAsStream(checkedTld);
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
builder.setEntityResolver(new ClasspathEntityResolver());
Document webXmlDoc = builder.parse(is);
is.close();
NodeList tagList = webXmlDoc.getElementsByTagName("tag");
List<TagAttribute> tagsAttributes = new ArrayList<TagAttribute>();
for (int i = 0; i < tagList.getLength(); i++)
{
Node tag = tagList.item(i);
// String tagclass = tag.getAttributes().getNamedItem("tag-class").getNodeValue();
NodeList tagAttributes = tag.getChildNodes();
String tagclass = null;
for (int k = 0; k < tagAttributes.getLength(); k++)
{
Node tagAttribute = tagAttributes.item(k);
// only handle 1.0 tlds
if ("tag-class".equals(tagAttribute.getNodeName()))
{
tagclass = tagAttribute.getChildNodes().item(0).getNodeValue();
break;
}
}
tagAttributes = tag.getChildNodes();
for (int k = 0; k < tagAttributes.getLength(); k++)
{
Node tagAttribute = tagAttributes.item(k);
if ("attribute".equals(tagAttribute.getNodeName()))
{
NodeList initParams = tagAttribute.getChildNodes();
String attributeName = null;
String attributeType = null;
for (int z = 0; z < initParams.getLength(); z++)
{
Node initParam = initParams.item(z);
if (initParam.getNodeType() != Node.TEXT_NODE && initParam.hasChildNodes())
{
if (initParam.getNodeName().equals("name"))
{
attributeName = initParam.getFirstChild().getNodeValue();
}
else if (initParam.getNodeName().equals("type"))
{
attributeType = initParam.getFirstChild().getNodeValue();
}
}
}
TagAttribute attribute = new TagAttribute();
attribute.setTagClass(tagclass);
attribute.setAttributeName(attributeName);
attribute.setAttributeType(attributeType);
tagsAttributes.add(attribute);
if (log.isDebugEnabled())
{
log.debug(attribute);
}
}
}
}
return tagsAttributes;
}
/**
* Simple Entity resolver which looks in the classpath for dtds.
* @author Fabrizio Giustina
* @version $Revision$ ($Author$)
*/
public static class ClasspathEntityResolver implements EntityResolver
{
/**
* @see org.xml.sax.EntityResolver#resolveEntity(java.lang.String, java.lang.String)
*/
public InputSource resolveEntity(String publicID, String systemID)
{
if (systemID != null)
{
String systemFileName = systemID;
if (systemFileName.indexOf("/") > 0)
{
systemFileName = systemFileName.substring(systemFileName.lastIndexOf("/") + 1, systemFileName
.length());
}
ClassLoader classLoader = getClass().getClassLoader();
URL dtdURL = classLoader.getResource("javax/servlet/jsp/resources/" + systemFileName);
if (dtdURL == null)
{
return null;
}
// Return local copy of the dtd
try
{
return new InputSource(dtdURL.openStream());
}
catch (IOException e)
{
// return null
}
}
// If no match, returning null makes process continue normally
return null;
}
}
/**
* Javabean representing a tag attribute.
* @author Fabrizio Giustina
* @version $Revision$ ($Author$)
*/
public static class TagAttribute
{
/**
* Tag class.
*/
private String tagClass;
/**
* Attribute name.
*/
private String attributeName;
/**
* Atttribute type (can be null).
*/
private String attributeType;
/**
* @return Returns the attribute name.
*/
public String getAttributeName()
{
return this.attributeName;
}
/**
* @param name attribute name.
*/
public void setAttributeName(String name)
{
this.attributeName = name;
}
/**
* @return Returns the attributeType.
*/
public String getAttributeType()
{
return this.attributeType;
}
/**
* @param type The attributeType to set.
*/
public void setAttributeType(String type)
{
this.attributeType = type;
}
/**
* @return Returns the tagClass.
*/
public String getTagClass()
{
return this.tagClass;
}
/**
* @param tagClassName name of the tag class
*/
public void setTagClass(String tagClassName)
{
this.tagClass = tagClassName;
}
/**
* @see java.lang.Object#toString()
*/
public String toString()
{
return new ToStringBuilder(this, ToStringStyle.SIMPLE_STYLE).append("tagClass", this.tagClass).append(
"attributeName",
this.attributeName).append("attributeType", this.attributeType).toString();
}
}
}