001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: * $Header:$
018: */
019: package org.apache.beehive.netui.core.urls;
020:
021: import java.io.PrintStream;
022: import java.util.ArrayList;
023: import java.util.Collections;
024: import java.util.Iterator;
025: import java.util.List;
026: import javax.servlet.ServletContext;
027: import javax.servlet.ServletRequest;
028: import javax.servlet.ServletResponse;
029:
030: import org.apache.beehive.netui.pageflow.scoping.ScopedServletUtils;
031: import org.apache.beehive.netui.util.internal.InternalStringBuilder;
032: import org.apache.beehive.netui.util.logging.Logger;
033:
034: /**
035: * Methods for registering URL rewriters, adding URL rewriters
036: * to the chain, and for rewriting URLs using registered rewriters.
037: *
038: * <p> Note that when a URLRewriter is registered with this service
039: * it is added to a chain (a List) of rewriters. When rewriting
040: * occurs, we loop through each rewriter in the list. The only exception
041: * to this is when a rewriter that does not allow other rewriters
042: * to be used is registered. This then becomes the exclusive rewriter
043: * to use and no other rewriters can be registered. </p>
044: *
045: * <p> The final step of the full rewriting process should be to run the
046: * rewritten URI through the templated URL formatting process. See
047: * {@link #getTemplatedURL} </p>
048: *
049: * <p> Also note that this API allows a client to register their own templated
050: * URI formatter so they can manage their own templates and formatting. </p>
051: */
052: public class URLRewriterService {
053: private static final Logger _log = Logger
054: .getInstance(URLRewriterService.class);
055:
056: private static final String URL_REWRITERS_KEY = "url_rewriters";
057: private static final String TEMPLATTED_URL_FORMATTER_KEY = "templated_url_formatter";
058:
059: /**
060: * Get the prefix to use when rewriting a query parameter name.
061: * Loops through the list of registered URLRewriters to build up a the prefix.
062: *
063: * @param servletContext the current ServletContext.
064: * @param request the current ServletRequest.
065: * @param name the name of the query parameter.
066: * @return a prefix to use to rewrite a query parameter name.
067: */
068: public static String getNamePrefix(ServletContext servletContext,
069: ServletRequest request, String name) {
070: ArrayList/*< URLRewriter >*/rewriters = getRewriters(request);
071:
072: InternalStringBuilder prefix = new InternalStringBuilder();
073:
074: if (rewriters != null) {
075: for (Iterator i = rewriters.iterator(); i.hasNext();) {
076: URLRewriter rewriter = (URLRewriter) i.next();
077: String nextPrefix = rewriter.getNamePrefix(
078: servletContext, request, name);
079: if (nextPrefix != null) {
080: prefix.append(nextPrefix);
081: }
082: }
083: }
084:
085: return prefix.toString();
086: }
087:
088: /**
089: * This method will return two bits of information that are used by components that want run through
090: * the AJAX facilities. The <code>AjaxUrlInfo</code> class is returned and specifies this information. Unlike
091: * the other URLRewriter method, this is a true Chain of Responsibility (CoR) implementation. The first URLRewriter
092: * to return the AjaxUrlInfo object wins and that is returned from this call. The reason for this is that
093: * the implementation of the Ajax Context is also a true CoR implementation. These must match.
094: * @param servletContext the current ServletContext.
095: * @param request the current ServletRequest.
096: * @param nameable this object that is the target of the Ajax request. Typically it is an INameable.
097: */
098: public static AjaxUrlInfo getAjaxUrl(ServletContext servletContext,
099: ServletRequest request, Object nameable) {
100: ArrayList/*< URLRewriter >*/rewriters = getRewriters(request);
101:
102: if (rewriters != null) {
103: for (Iterator i = rewriters.iterator(); i.hasNext();) {
104: URLRewriter rewriter = (URLRewriter) i.next();
105: AjaxUrlInfo info = rewriter.getAjaxUrl(servletContext,
106: request, nameable);
107: if (info != null)
108: return info;
109: }
110: }
111: return null;
112: }
113:
114: /**
115: * Rewrite the given URL, looping through the list of registered URLRewriters.
116: *
117: * <p> Once the MutableURI has been rewritten, and if it is an instance of
118: * {@link FreezableMutableURI}, then this method will set the URI to a frozen
119: * state. I.e. immutable. If a user then tries to use a setter method on the
120: * rewritten URI, the FreezableMutableURI will throw an IllegalStateException. </p>
121: *
122: * <p> Note that after the rewritting the caller should run the rewritten URI
123: * through the templated URI formatting process as the last step in rewriting.
124: * See {@link #getTemplatedURL} </p>
125: *
126: * @param servletContext the current ServletContext.
127: * @param request the current ServletRequest.
128: * @param response the current ServletResponse.
129: * @param url the URL to be rewritten.
130: * @param type the type of URL to be rewritten. This is one of the following values:
131: * <ul>
132: * <li><code>action</code>: a standard (non-resource) URL
133: * <li><code>resource</code>: a resource (e.g., image) URL
134: * </ul>
135: * @param needsToBeSecure a flag indicating whether the URL should be secure (SSL required) or not
136: * @see #registerURLRewriter
137: */
138: public static void rewriteURL(ServletContext servletContext,
139: ServletRequest request, ServletResponse response,
140: MutableURI url, URLType type, boolean needsToBeSecure) {
141: ArrayList/*< URLRewriter >*/rewriters = getRewriters(request);
142:
143: if (rewriters != null) {
144: for (Iterator i = rewriters.iterator(); i.hasNext();) {
145: URLRewriter rewriter = (URLRewriter) i.next();
146: rewriter.rewriteURL(servletContext, request, response,
147: url, type, needsToBeSecure);
148: }
149: }
150:
151: if (url instanceof FreezableMutableURI) {
152: ((FreezableMutableURI) url).setFrozen(true);
153: }
154: }
155:
156: /**
157: * Get the unmodifiable list of URLRewriter objects in the request that will be used if
158: * {@link #rewriteURL} is called.
159: *
160: * @param request the current ServletRequest.
161: * @return an unmodifiable list of the URLRewriters that have been registered.
162: */
163: public static List/*< URLRewriter >*/getURLRewriters(
164: ServletRequest request) {
165: return Collections.unmodifiableList(getRewriters(request));
166: }
167:
168: /**
169: * Register a URLRewriter (add to a list) in the request. It will be added to the end
170: * of a list of URLRewriter objects and will be used if {@link #rewriteURL} is called.
171: *
172: * @param request the current ServletRequest.
173: * @param rewriter the URLRewriter to register.
174: * @return <code>false</code> if a URLRewriter has been registered
175: * that does not allow other rewriters. Otherwise, <code>true</code>
176: * if the URLRewriter was added to the chain or already exists in
177: * the chain.
178: */
179: public static boolean registerURLRewriter(ServletRequest request,
180: URLRewriter rewriter) {
181: ArrayList/*< URLRewriter >*/rewriters = getRewriters(request);
182:
183: if (rewriters == null) {
184: rewriters = new ArrayList/*< URLRewriter >*/();
185: rewriters.add(rewriter);
186: request.setAttribute(URL_REWRITERS_KEY, rewriters);
187: } else {
188: return addRewriter(rewriters, rewriter, rewriters.size());
189: }
190:
191: return true;
192: }
193:
194: /**
195: * Register a URLRewriter (add to a list) in the request. It will be added at the
196: * specified position in this list of URLRewriter objects and will be used if
197: * {@link #rewriteURL} is called.
198: *
199: * @param index the place to insert the URLRewriter
200: * @param request the current ServletRequest.
201: * @param rewriter the URLRewriter to register.
202: * @return <code>false</code> if a URLRewriter has been registered
203: * that does not allow other rewriters. Otherwise, <code>true</code>
204: * if the URLRewriter was added to the chain or already exists in
205: * the chain.
206: */
207: public static boolean registerURLRewriter(int index,
208: ServletRequest request, URLRewriter rewriter) {
209: ArrayList/*< URLRewriter >*/rewriters = getRewriters(request);
210:
211: if (rewriters == null) {
212: rewriters = new ArrayList/*< URLRewriter >*/();
213: rewriters.add(rewriter);
214: request.setAttribute(URL_REWRITERS_KEY, rewriters);
215: } else {
216: return addRewriter(rewriters, rewriter, index);
217: }
218:
219: return true;
220: }
221:
222: private static ArrayList/*< URLRewriter >*/getRewriters(
223: ServletRequest request) {
224: return (ArrayList/*< URLRewriter >*/) ScopedServletUtils
225: .getScopedRequestAttribute(URL_REWRITERS_KEY, request);
226: }
227:
228: private static boolean addRewriter(
229: ArrayList/*< URLRewriter >*/rewriters,
230: URLRewriter rewriter, int index) {
231: if (otherRewritersAllowed(rewriters)) {
232: if (!rewriters.contains(rewriter)) {
233: if (!rewriter.allowOtherRewriters()) {
234: rewriters.clear();
235:
236: if (rewriters.size() > 0 && _log.isDebugEnabled()) {
237: InternalStringBuilder message = new InternalStringBuilder();
238: message
239: .append("Register exclusive URLRewriter, \"");
240: message.append(rewriter.getClass().getName());
241: message
242: .append("\". This removes any other URLRewriter objects already registered in the chain.");
243: _log.debug(message.toString());
244: }
245: }
246: rewriters.add(index, rewriter);
247: }
248: } else {
249: if (_log.isDebugEnabled()) {
250: InternalStringBuilder message = new InternalStringBuilder();
251: message.append("Cannot register URLRewriter, \"");
252: message.append(rewriter.getClass().getName());
253: message.append("\". The URLRewriter, \"");
254: message.append(rewriters.get(0).getClass().getName());
255: message
256: .append("\", is already registered and does not allow other rewriters.");
257: _log.debug(message.toString());
258: }
259:
260: return false;
261: }
262:
263: return true;
264: }
265:
266: private static boolean otherRewritersAllowed(
267: ArrayList/*< URLRewriter >*/rewriters) {
268: if (rewriters != null
269: && rewriters.size() == 1
270: && !((URLRewriter) rewriters.get(0))
271: .allowOtherRewriters()) {
272: return false;
273: }
274:
275: return true;
276: }
277:
278: /**
279: * Unregister the URLRewriter (remove from the list) from the request.
280: *
281: * @param request the current ServletRequest.
282: * @param rewriter the URLRewriter to unregister
283: * @see #registerURLRewriter
284: */
285: public static void unregisterURLRewriter(ServletRequest request,
286: URLRewriter rewriter) {
287: if (rewriter == null) {
288: return;
289: }
290:
291: ArrayList/*< URLRewriter >*/rewriters = getRewriters(request);
292:
293: if (rewriters == null) {
294: return;
295: } else {
296: rewriters.remove(rewriter);
297:
298: if (rewriters.size() == 0) {
299: request.removeAttribute(URL_REWRITERS_KEY);
300: }
301: }
302: }
303:
304: /**
305: * Unregister the URLRewriter (remove from the list) from the request.
306: *
307: * @param request the current ServletRequest.
308: */
309: public static void unregisterAllURLRewriters(ServletRequest request) {
310: request.removeAttribute(URL_REWRITERS_KEY);
311: }
312:
313: /**
314: * Tell whether rewritten form actions should be allowed to have query parameters. If this returns
315: * <code>false</code>, then a form-tag implementation should render query parameters into hidden
316: * fields on the form instead of allowing them to remain in the URL.
317: */
318: public static boolean allowParamsOnFormAction(
319: ServletContext servletContext, ServletRequest request) {
320: ArrayList/*< URLRewriter >*/rewriters = getRewriters(request);
321:
322: if (rewriters != null) {
323: for (Iterator i = rewriters.iterator(); i.hasNext();) {
324: URLRewriter rewriter = (URLRewriter) i.next();
325: if (!rewriter.allowParamsOnFormAction(servletContext,
326: request)) {
327: return false;
328: }
329: }
330: }
331:
332: return true;
333: }
334:
335: /**
336: * Print out information about the chain of URLRewriters in this request.
337: *
338: * @param request the current HttpServletRequest.
339: * @param output a PrintStream to output chain of URLRewriters in this request.
340: * If <code>null</null>, <code>System.err</code> is used.
341: */
342: public static void dumpURLRewriters(ServletRequest request,
343: PrintStream output) {
344: ArrayList/*< URLRewriter >*/rewriters = getRewriters(request);
345:
346: if (output == null)
347: output = System.err;
348: output.println("*** List of URLRewriter objects: " + rewriters);
349:
350: if (rewriters != null) {
351: int count = 0;
352: for (Iterator i = rewriters.iterator(); i.hasNext();) {
353: URLRewriter rewriter = (URLRewriter) i.next();
354: output.println(" " + count++ + ". "
355: + rewriter.getClass().getName());
356: output.println(" allows other rewriters: "
357: + rewriter.allowOtherRewriters());
358: output.println(" rewriter: " + rewriter);
359: }
360: } else {
361: output
362: .println(" No URLRewriter objects are registered with this request.");
363: }
364: }
365:
366: /**
367: * Format the given URI using a URL template, if defined in the URL template
368: * config file, WEB-INF/url-template-config.xml. The {@link URIContext}
369: * encapsulates some additional data needed to write out the string form.
370: * E.g. It defines if the "&amp;" entity or the
371: * '&' character should be used to separate quary parameters.
372: *
373: * <p>First try to use ther per-request registered <code>TemplatedURLFormatter</code>.
374: * If one is not registered, try to use the per-webapp default
375: * <code>TemplatedURLFormatter</code>, defined in beehive-netui-config.xml
376: * (with a class name) and set as an attribute of the ServletContext. Otherwise,
377: * with no formatter, just return {@link MutableURI#getURIString(URIContext)}.
378: *
379: * @param servletContext the current ServletContext.
380: * @param request the current ServletRequest.
381: * @param uri the MutableURI to be formatted into a String.
382: * @param key the URL template type to use for formatting the URI
383: * @param uriContext data required to write out the string form.
384: * @return the URL as a <code>String</code>
385: */
386: public static String getTemplatedURL(ServletContext servletContext,
387: ServletRequest request, MutableURI uri, String key,
388: URIContext uriContext) {
389: TemplatedURLFormatter formatter = getTemplatedURLFormatter(request);
390: if (formatter == null) {
391: formatter = TemplatedURLFormatter
392: .getTemplatedURLFormatter(request);
393: if (formatter == null) {
394: return uri.getURIString(uriContext);
395: }
396: }
397:
398: return formatter.getTemplatedURL(servletContext, request, uri,
399: key, uriContext);
400: }
401:
402: private static TemplatedURLFormatter getTemplatedURLFormatter(
403: ServletRequest request) {
404: return (TemplatedURLFormatter) ScopedServletUtils
405: .getScopedRequestAttribute(
406: TEMPLATTED_URL_FORMATTER_KEY, request);
407: }
408:
409: /**
410: * Register a TemplatedURLFormatter in the request.
411: *
412: * <p> The TemplatedURLFormatter should be used as a final step in the rewriting
413: * process to format the rewritten URL as defined by a template in the
414: * WEB-INF/url-template-config.xml. There can only be one TemplatedURLFormatter,
415: * not a chain as with the URLRewriters. </p>
416: *
417: * @param request the current ServletRequest.
418: * @param formatter the TemplatedURLFormatter to register.
419: */
420: public static void registerTemplatedURLFormatter(
421: ServletRequest request, TemplatedURLFormatter formatter) {
422: request.setAttribute(TEMPLATTED_URL_FORMATTER_KEY, formatter);
423: }
424:
425: /**
426: * Unregister the TemplatedURLFormatter from the request.
427: *
428: * @param request the current ServletRequest.
429: */
430: public static void unregisterTemplatedURLFormatter(
431: ServletRequest request) {
432: request.removeAttribute(TEMPLATTED_URL_FORMATTER_KEY);
433: }
434: }
|