DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
  1. DZone
  2. Coding
  3. Languages
  4. Hacking Jasper to Get Object Model of a JSP Page

Hacking Jasper to Get Object Model of a JSP Page

Jakub Holý user avatar by
Jakub Holý
·
Jun. 13, 11 · Interview
Like (0)
Save
Tweet
Share
6.16K Views

Join the DZone community and get the full member experience.

Join For Free

to perform some checks and statistical analysis on my jsps i needed a dom-like, hierarchical model of elements contained in them. but parsing jsp pages isn’t trivial and is best left to a tool that excels in it – the jasper jsp compiler used by tomcat, jetty, glassfish and likely also by all others. there is an easy way to tweak it to produce whatever output you need nad to transform a jsp into whatever form you want, including an object model of the page:

  1. define a node.visitor subclass for handling the nodes (tags etc.) of a jsp
  2. write a simple subclass of compiler, overriding its generatejava() to invoke the visitor
  3. subclass the compiler executor jspc overriding its method getcompilerclassname() to return the class of the compiler of yours

let’s see the code.

implementation

1. custom visitor

a visitor is invoked by the compiler to process a tree object model of a parsed jsp. this implementation just prints information about an interesting subset of nodes in the page, indented to make their nesting clear.

package org.apache.jasper.compiler;

import java.util.linkedlist;
import org.apache.jasper.jasperexception;
import org.apache.jasper.compiler.node.customtag;
import org.apache.jasper.compiler.node.elexpression;
import org.apache.jasper.compiler.node.includedirective;
import org.apache.jasper.compiler.node.visitor;
import org.xml.sax.attributes;

public class jsfelcheckingvisitor extends visitor {

private string indent = "";

@override
public void visit(elexpression n) throws jasperexception {
logentry("elexpression", n, "el: " + n.getel());
super.visit(n);
}

@override
public void visit(includedirective n) throws jasperexception {
logentry("includedirective", n, tostring(n.getattributes()));
super.visit(n);
}

@override
public void visit(customtag n) throws jasperexception {
logentry("customtag", n, "class: " + n.gettaghandlerclass().getname() + ", attrs: "
+ tostring(n.getattributes()));

dovisit(n);

indent += " ";
visitbody(n);
indent = indent.substring(0, indent.length() - 1);
}

private string tostring(attributes attributes) {
if (attributes == null || attributes.getlength() == 0) return "";
linkedlist<string> details = new linkedlist<string>();

for (int i = 0; i < attributes.getlength(); i++) {
details.add(attributes.getqname(i) + "=" + attributes.getvalue(i));
}

return details.tostring();
}

private void logentry(string what, node n, string details) {
system.out.println(indent + n.getqname() + " at line:"
+ n.getstart().getlinenumber() + ": " + details);
}

}

notes:

  • the visitor must be in the org.apache.jasper.compiler package because the essential class org.apache.jasper.compiler.node is package-private
  • the method visitbody triggers processing of the nested nodes
  • there are more methods i could have overridden (and the catch-all method dovisit ) but i’ve selected only those interesting for me
  • the node’s attributes are of the type …sax. attributes , which contains attribute names and values as strings
    • attributes.gettype(i) is usually cdata
  • the node structure contains information about the parent node, tag name, tag handler class, the corresponding line of the source file and the name of the source file and other useful information
  • customtag is likely the most interesting node type, e.g. all the jsf tags are of this type

example output (for a jsf page)

jsp:directive.include at line:5: [file=includes/stdjsp.jsp]
jsp:directive.include at line:6: [file=includes/ssoinclude.jsp]
f:verbatim at line:14: class: com.sun.faces.taglib.jsf_core.verbatimtag, attrs:
htm:div at line:62: class: com.exadel.htmlib.tags.divtag, attrs: [style=width:100%;]
h:form at line:64: class: com.sun.faces.taglib.html_basic.formtag, attrs: [id=inputform]
htm:table at line:66: class: com.exadel.htmlib.tags.tabletag, attrs: [cellpadding=0, width=100%, border=0, styleclass=clear box_main]
htm:tr at line:71: class: com.exadel.htmlib.tags.trtag, attrs:
htm:td at line:72: class: com.exadel.htmlib.tags.tdtag, attrs:
f:subview at line:73: class: com.sun.faces.taglib.jsf_core.subviewtag, attrs: [id=cars]
jsp:directive.include at line:74: [file=/includes/cars.jsp]
h:panelgroup at line:8: class: com.sun.faces.taglib.html_basic.panelgrouptag, attrs: [rendered=#{bookinghandler.flowersavailable}]
...
htm:tr at line:87: class: com.exadel.htmlib.tags.trtag, attrs: [style=height:5px]
htm:td at line:87: class: com.exadel.htmlib.tags.tdtag, attrs:

(i do not print “closing tags” for it’s clear that a tag ends when another node with the same or smaller indentation appears or the output ends.)

2. compiler subclass

the important part is generatejava , which i have just copied, removed some code from it and added an invocation of my visitor . so actually only 3 lines in the listing below are new.

public class onlyreadingjsppseudocompiler extends compiler {

/** we're never compiling .java to .class. */
@override protected void generateclass(string[] smap) throws filenotfoundexception,
jasperexception, exception {
return;
}

/** copied from {@link compiler#generatejava()} and adjusted */
@override protected string[] generatejava() throws exception {

// setup page info area
pageinfo = new pageinfo(new beanrepository(ctxt.getclassloader(),
errdispatcher), ctxt.getjspfile());

// jh: skipped processing of jsp-property-group in web.xml for the current page

if (ctxt.istagfile()) {
try {
double libraryversion = double.parsedouble(ctxt.gettaginfo()
.gettaglibrary().getrequiredversion());
if (libraryversion < 2.0) {
pageinfo.setiselignored("true", null, errdispatcher, true);
}
if (libraryversion < 2.1) {
pageinfo.setdeferredsyntaxallowedasliteral("true", null,
errdispatcher, true);
}
} catch (numberformatexception ex) {
errdispatcher.jsperror(ex);
}
}

ctxt.checkoutputdir();

try {
// parse the file
parsercontroller parserctl = new parsercontroller(ctxt, this);

// pass 1 - the directives
node.nodes directives =
parserctl.parsedirectives(ctxt.getjspfile());
validator.validatedirectives(this, directives);

// pass 2 - the whole translation unit
pagenodes = parserctl.parse(ctxt.getjspfile());

// validate and process attributes - don't re-validate the
// directives we validated in pass 1
/**
* jh: the code above has been copied from compiler#generatejava() with some
* omissions and with using our own visitor.
* the code that used to follow was just deleted.
* note: the jsp's name is in ctxt.getjspfile()
*/
pagenodes.visit(new jsfelcheckingvisitor());

} finally {}

return null;
}

/**
* the parent's implementation, in our case, checks whether the target file
* exists and returns true if it doesn't. however it is expensive so
* we skip it by returning true directly.
* @see org.apache.jasper.jspcompilationcontext#getservletjavafilename()
*/
@override public boolean isoutdated(boolean checkclass) {
return true;
}

}

notes:

  • i have deleted quite lot of code unimportant for me from generate java; for a different type of analysis than i intend some of that code could have been useful, so look into the original compiler class and decide for yourself.
  • i do not really care about jsp els so it might be possible to optimize the compiler to need only one pass.

3. compiler executor

it is difficult to use a compiler directly because it depends on quite a number of complex settings and objects. the easiest thing is thus to reuse the ant task jspc, which has the additional benefit of finding the jsps to process. as mentioned, the key thing is the overriding of getcompilerclassname to return my compiler’s class.

import org.apache.jasper.jspc;

/** extends jspc to use the compiler of our choice; jasper version 6.0.29. */
public class jspcparsingtonodesonly extends jspc {

/** overriden to return the class of ours (default = null => jdtcompiler) */
@override public string getcompilerclassname() {
return onlyreadingjsppseudocompiler.class.getname();
}

public static void main(string[] args) {
jspcparsingtonodesonly jspc = new jspcparsingtonodesonly();

jspc.seturiroot("web"); // where to search for jsps
//jspc.setverbose(1); // 0 = false, 1 = true
jspc.setjspfiles("hellojsfpage.jsp"); // leave unset to process all; comma-separated

try {
jspc.execute();
} catch (jasperexception e) {
throw new runtimeexception(e);
}
}
}

notes:

  • jspc normally finds all files under the specified uriroot but you can tell it to ignore all but some selected ones by passing their comma-separated names into setjspfiles .

compile dependencies

in thy ivy form:

<dependency org="org.apache.tomcat" name="jasper" rev="6.0.29" />
<dependency org="org.apache.tomcat" name="jasper-jdt" rev="6.0.29" />
<dependency org="org.apache.ant" name="ant" rev="1.8.2" />

license

all the code here is directly derived from jasper and thus falls under the same license, i.e. the apache license, version 2.0 .

conclusion

jasper wasn’t really designed for extension and modularity as documented by the fact that the crucial node class is package private and by its api being so complex that reusing just a part of it is very hard. fortunately the ant task jspc makes it usable outside of a servlet container by providing some “fake” objects and there is a way to tweak it to our needs with very little work though it wasn’t that easy to figure it out :-) . i had to apply some dirty tricks, namely using stuff from a package-private class and overriding a method not intended to be overriden ( generatejava ) but it works and provides very valuable output, which makes it possible to do just anything you might want to do with the a jsp.

from http://theholyjava.wordpress.com/2011/06/10/hacking-jasper-to-get-object-model-of-a-jsp-page/

Object model Object (computer science) JasPer

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Metrics Part 2: The DORA Keys
  • All the Cloud’s a Stage and All the WebAssembly Modules Merely Actors
  • Collaborative Development of New Features With Microservices
  • Strategies for Kubernetes Cluster Administrators: Understanding Pod Scheduling

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: