1 /*
2 * Title: ApplyDecoratorTag
3 * Description:
4 *
5 * This software is published under the terms of the OpenSymphony Software
6 * License version 1.1, of which a copy has been included with this
7 * distribution in the LICENSE.txt file.
8 */
9
10 package com.opensymphony.module.sitemesh.taglib.page;
11
12 import com.opensymphony.module.sitemesh;
13 import com.opensymphony.module.sitemesh.filter.PageRequestWrapper;
14 import com.opensymphony.module.sitemesh.filter.PageResponseWrapper;
15
16 import javax.servlet.RequestDispatcher;
17 import javax.servlet.ServletException;
18 import javax.servlet.http.HttpServletRequest;
19 import javax.servlet.http.HttpServletResponse;
20 import javax.servlet.jsp.JspException;
21 import javax.servlet.jsp.tagext.BodyTagSupport;
22 import java.io.IOException;
23 import java.io.BufferedReader;
24 import java.io.InputStreamReader;
25 import java.util.HashMap;
26 import java.util.Iterator;
27 import java.util.Map;
28 import java.net.URL;
29 import java.net.URLConnection;
30 import java.net.MalformedURLException;
31
32 /**
33 * This tag inserts an external resource as a panel into the current Page.
34 *
35 * <p>The page attribute should point to the panel resource
36 * which should expose an entire page (e.g. another JSP file producing
37 * HTML). This attribute can be relative to the page it is being called
38 * from or an absolute path from the context-root.</p>
39 *
40 * <p><strong>OR</strong></p>
41 *
42 * <p>If the page attribute is not specified, the body content is parsed
43 * into the {@link com.opensymphony.module.sitemesh.Page} object and has
44 * the {@link com.opensymphony.module.sitemesh.Decorator} applied.</p>
45 *
46 * <p>The (optional) decorator attribute is the name of the
47 * {@link com.opensymphony.module.sitemesh.Decorator}
48 * to apply to the included page. Note that the implementation of
49 * {@link com.opensymphony.module.sitemesh.DecoratorMapper} can overide this.</p>
50 *
51 * @author <a href="mailto:joe@truemesh.com">Joe Walnes</a>
52 * @version $Revision: 1.6 $
53 */
54 public class ApplyDecoratorTag extends BodyTagSupport implements RequestConstants {
55 private String page = null;
56 private String decorator = null;
57
58 private String contentType = null;
59 private String encoding = null;
60
61 private Map params = new HashMap(6);
62 private Config config = null;
63 private DecoratorMapper decoratorMapper = null;
64 private Factory factory;
65
66 /**
67 * Tag attribute: URI of page to include.
68 * Can be relative to page being called from, or absolute
69 * path from context-root of web-app.
70 */
71 public void setPage(String page) {
72 this.page = page;
73 }
74
75 /**
76 * Add a parameter to the page. This has a package level
77 * access modifier so ParamTag can also call it.
78 */
79 void addParam(String name, String value) {
80 params.put(name, value);
81 }
82
83 /**
84 * Tag attribute: If set, this value will override the 'title'
85 * property of the page. This is a convenience utility and is
86 * identical to specifing a 'page:param name=title' tag.
87 */
88 public void setTitle(String title) {
89 addParam("title", title);
90 }
91
92 /**
93 * Tag attribute: If set, this value will override the 'id'
94 * property of the page. This is a convenience utility and is
95 * identical to specifing a 'page:param name=id' tag.
96 */
97 public void setId(String id) {
98 addParam("id", id);
99 }
100
101 /**
102 * Tag attribute: Name of Decorator to apply to Page.
103 * This is passed to DecoratorMapper to retrieve appropriate
104 * Decorator. DecoratorMapper may override if needed.
105 *
106 * @see com.opensymphony.module.sitemesh.DecoratorMapper
107 */
108 public void setName(String decorator) {
109 if (decorator != null) this.decorator = decorator;
110 }
111
112 /** @deprecated Use setName() instead. */
113 public void setDecorator(String decorator) {
114 setName(decorator);
115 }
116
117 public void setContentType(String contentType) {
118 this.contentType = contentType;
119 }
120
121 public void setEncoding(String encoding) {
122 this.encoding = encoding;
123 }
124
125 public int doStartTag() {
126 if (config == null) {
127 // set context if not already set
128 config = new Config(pageContext.getServletConfig());
129 factory = Factory.getInstance(config);
130 decoratorMapper = factory.getDecoratorMapper();
131 }
132 // return page == null ? EVAL_BODY_BUFFERED : SKIP_BODY;
133 return EVAL_BODY_BUFFERED;
134 }
135
136 /** Ensure that external page contents are included in bodycontent. */
137 public int doAfterBody() throws JspException {
138 return SKIP_BODY;
139 }
140
141 /** Standard taglib method: apply decorator to page. */
142 public int doEndTag() throws JspException {
143 try {
144 // if composite decorator, remember last page
145 Page oldPage = (Page) pageContext.getRequest().getAttribute(PAGE);
146
147 // parse bodycontent into Page object
148 PageParser parser = Factory.getInstance(config).getPageParser(contentType != null ? contentType : "text/html");
149 Page pageObj = null;
150
151 if (page == null) {
152 // inline content
153 pageObj = parser.parse(bodyContent.getString().toCharArray());
154 }
155 else if (page.startsWith("http://") || page.startsWith("https://")) {
156 try {
157 URL url = new URL(page);
158 URLConnection urlConn = url.openConnection();
159 urlConn.setUseCaches(true);
160 BufferedReader in = new BufferedReader(
161 new InputStreamReader(urlConn.getInputStream()));
162
163 StringBuffer sbuf = new StringBuffer();
164 char[] buf = new char[1000];
165 for (; ;) {
166 int moved = in.read(buf);
167 if (moved < 0) break;
168 sbuf.append(buf, 0, moved);
169 }
170 in.close();
171 pageObj = parser.parse(sbuf.toString().toCharArray());
172 }
173 catch (MalformedURLException e) {
174 trace(e);
175 throw new JspException(e.toString());
176 }
177 catch (IOException e) {
178 trace(e);
179 throw new JspException(e.toString());
180 }
181 }
182 else {
183 // external content
184 String fullPath = page;
185 if (fullPath.length() > 0 && fullPath.charAt(0) != '/') {
186 HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
187
188 // find absolute path if relative supplied
189 String thisPath = request.getServletPath();
190
191 // check if it did not return null (could occur when the servlet container
192 // does not use a servlet to serve the requested resouce)
193 if (thisPath == null) {
194 String requestURI = request.getRequestURI();
195 if (request.getPathInfo() != null) {
196 // strip the pathInfo from the requestURI
197 thisPath = requestURI.substring(0, requestURI.indexOf(request.getPathInfo()));
198 }
199 else {
200 thisPath = requestURI;
201 }
202 }
203
204 fullPath = thisPath.substring(0, thisPath.lastIndexOf('/') + 1) + fullPath;
205 int dotdot;
206 while ((dotdot = fullPath.indexOf("..")) > -1) {
207 int prevSlash = fullPath.lastIndexOf('/', dotdot - 2);
208 fullPath = fullPath.substring(0, prevSlash) + fullPath.substring(dotdot + 2);
209 }
210 }
211
212 // include page using filter response
213 RequestDispatcher rd = pageContext.getServletContext().getRequestDispatcher(fullPath);
214 PageRequestWrapper pageRequest = new PageRequestWrapper((HttpServletRequest) pageContext.getRequest());
215 PageResponseWrapper pageResponse = new PageResponseWrapper((HttpServletResponse) pageContext.getResponse(), factory);
216
217 StringBuffer sb = new StringBuffer(contentType != null ? contentType : "text/html");
218 if (encoding != null) {
219 sb.append(";charset=").append(encoding);
220 }
221 pageResponse.setContentType(sb.toString());
222
223 // if rd == null, then the panel was not found, but this isn't correct, so we need to spit out
224 // something appropriate. What this is, well...I don't know yet.
225 if (rd == null) {
226 throw new ApplyDecoratorException("The specified resource in applyDecorator tag (" + fullPath + ") was not found.");
227 }
228 rd.include(pageRequest, pageResponse);
229 pageObj = pageResponse.getPage();
230 }
231
232 // If pageObj == null, then the panel source had some weird error in
233 // it. Stop writing bugs like this. They're ugly and they make you smell funny.
234 if (pageObj == null) {
235 throw new ApplyDecoratorException(page + " did not create a valid page to decorate.");
236 }
237
238 // add extra params to Page
239 Iterator paramKeys = params.keySet().iterator();
240 while (paramKeys.hasNext()) {
241 String k = (String) paramKeys.next();
242 String v = (String) params.get(k);
243 pageObj.addProperty(k, v);
244 }
245
246 // get decorator
247 if (decorator == null) decorator = "";
248 pageObj.setRequest((HttpServletRequest) pageContext.getRequest());
249 pageContext.getRequest().setAttribute(DECORATOR, decorator);
250 Decorator d = decoratorMapper.getDecorator(((HttpServletRequest) pageContext.getRequest()), pageObj);
251 pageContext.getRequest().removeAttribute(DECORATOR);
252
253 // apply decorator
254 if (d != null && d.getPage() != null) {
255 pageContext.getRequest().setAttribute(PAGE, pageObj);
256 pageContext.include(d.getPage());
257 }
258 else {
259 throw new JspException("Cannot locate inline Decorator: " + decorator);
260 }
261
262 // clean up
263 pageContext.getRequest().setAttribute(PAGE, oldPage);
264 }
265 catch (IOException e) {
266 trace(e);
267 throw new JspException(e.toString());
268 }
269 catch (ServletException e) {
270 trace(e);
271 throw new JspException(e.toString());
272 }
273 catch (ApplyDecoratorException e) {
274 try {
275 pageContext.getOut().println(e.getMessage());
276 }
277 catch (IOException ioe) {
278 System.err.println("IOException thrown in applyDecorator tag: " + e.toString());
279 }
280 }
281 return EVAL_PAGE;
282 }
283
284 /** Release all attributes */
285 public void release() {
286 params.clear();
287 super.release();
288 }
289
290 /** Log exception generated by taglib. */
291 public static void trace(Exception e) {
292 e.printStackTrace();
293 }
294
295 class ApplyDecoratorException extends Exception {
296 public ApplyDecoratorException(String s) {
297 super(s);
298 }
299 }
300 }