package net.techreadiness.ui.tags;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.DynamicAttributes;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.JspTag;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import org.apache.commons.io.output.NullWriter;
import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.Maps;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.util.TextParseUtil;
import com.opensymphony.xwork2.util.ValueStack;
public abstract class BaseTag extends SimpleTagSupport implements DynamicAttributes {
private final Map<String, Object> dynamicAttributes = Maps.newHashMap();
private Map<String, Object> actionContext = null;
public BaseTag() {
ActionContext.getContext().getContainer().inject(this);
}
/**
* perform any logic prior to rendering tag jsp
*
* @return path to tag's jsp, return null to render nothing.
* @throws Exception
*/
protected String execute() throws Exception {
return null;
}
@Override
public void doTag() throws JspException, IOException {
try {
JspTag parent = getOptionalParentTag(ParentTag.class);
Map<String, Object> actionContext = ActionContext.getContext().getValueStack().getContext();
if (parent != null && parent instanceof ParentTag && ((ParentTag) parent).isCollectingChildren()) {
this.actionContext = new HashMap<>(actionContext);
((ParentTag) parent).getChildren().add(this);
if (this instanceof ParentTag) {
((ParentTag) this).collectChildren();
}
} else {
if (this instanceof ParentTag) {
((ParentTag) this).collectChildren();
}
String jspResult = execute();
if (jspResult != null) {
renderPage(jspResult);
}
}
} catch (Exception e) {
printStackTrace(e);
}
}
protected String evaluateOgnl(final String ognl) {
return executeWithinOriginalContext(new Callable<String>() {
@Override
public String call() throws Exception {
return TextParseUtil.translateVariables('%', StringUtils.defaultString(ognl), getValueStack());
}
});
}
protected <T> T evaluateOgnl(final String ognl, final Class<T> clazz) {
return executeWithinOriginalContext(new Callable<T>() {
@SuppressWarnings("unchecked")
@Override
public T call() throws Exception {
return (T) TextParseUtil.translateVariables('%', StringUtils.defaultString(ognl), getValueStack(), clazz);
}
});
}
private void printStackTrace(Exception e) throws IOException {
getJspContext().getOut().println("<textarea style=\"width:100%;height:400\">");
e.printStackTrace(new PrintWriter(getJspContext().getOut()));
e.printStackTrace();
getJspContext().getOut().println("</textarea>");
}
private void renderPage(String location) throws IOException, ServletException {
Object prevTag = getPageContext().getRequest().getAttribute("tag");
ValueStack stack = getValueStack();
try {
getPageContext().getRequest().setAttribute("tag", this);
if (stack != null) {
stack.push(this);
}
getPageContext().include(location);
} finally {
getPageContext().getRequest().setAttribute("tag", prevTag);
if (stack != null) {
stack.pop();
}
}
}
public ValueStack getValueStack() {
return (ValueStack) getRequest().getAttribute("struts.valueStack");
}
/**
* Evaluate the body of this tag, but do not write the results to any page.
* @throws JspException If there is an error invoking the body.
* @throws IOException If there is an error writing to the stream.
*/
public final void invokeBody() throws JspException, IOException {
if (getJspBody() != null) {
try (Writer writer = new NullWriter()) {
getJspBody().invoke(writer);
}
}
}
/**
* Write the body of this tag to a jspWriter. This is exposed so that tags can be written multiple times.
*
* @param writer
* The writer that the body of the tag is written to.
* @throws JspException
* Thrown if an error occurred while invoking the tag body.
* @throws IOException
* If there was an error writing to the {@code writer}.
*/
public final void writeBody(final Writer writer) throws JspException, IOException {
executeWithinOriginalContext(new Callable<Void>() {
@Override
public Void call() throws Exception {
if (getJspBody() != null) {
getJspBody().invoke(writer);
}
return null;
}
});
}
public final <T> T getRequiredParentTag(Class<T> parentTagClass) {
@SuppressWarnings("unchecked")
T parentTag = (T) findAncestorWithClass(this, parentTagClass);
if (parentTag == null) {
throw new RuntimeException(this.getClass().getSimpleName() + " must be surrounded by a "
+ parentTagClass.getSimpleName());
}
return parentTag;
}
public final <T> T getOptionalParentTag(Class<T> parentTagClass) {
@SuppressWarnings("unchecked")
T parentTag = (T) findAncestorWithClass(this, parentTagClass);
return parentTag;
}
protected PageContext getPageContext() {
return (PageContext) getJspContext();
}
@Override
public JspFragment getJspBody() {
return super.getJspBody();
}
public HttpServletRequest getRequest() {
return (HttpServletRequest) getPageContext().getRequest();
}
public HttpServletResponse getResponse() {
return (HttpServletResponse) getPageContext().getResponse();
}
public ServletContext getServletContext() {
return getPageContext().getServletContext();
}
public HttpSession getSession() {
return getRequest().getSession();
}
@Override
public void setDynamicAttribute(String uri, String localName, Object value) throws JspException {
dynamicAttributes.put(localName, value);
}
public Map<String, Object> getDynamicAttributes() {
return dynamicAttributes;
}
public Map<String, Object> getActionContext() {
return actionContext;
}
/**
* Execute a block of code within the original context of this tag. When the tag is a parent tag, we must restore it's
* original context for certain operations that require the ognl context.
*/
private <T> T executeWithinOriginalContext(Callable<T> execution) {
Map<String, Object> contextBak = Maps.newHashMap(ActionContext.getContext().getValueStack().getContext());
try {
if (actionContext != null) {
ActionContext.getContext().getValueStack().getContext().putAll(actionContext);
}
return execution.call();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
if (actionContext != null) {
ActionContext.getContext().getValueStack().getContext().putAll(contextBak);
}
}
}
}