1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18
19 package org.apache.catalina.core;
20
21
22 import java.io.File;
23 import java.io.InputStream;
24 import java.net.MalformedURLException;
25 import java.net.URL;
26 import java.util.ArrayList;
27 import java.util.Enumeration;
28 import java.util.Iterator;
29 import java.util.Map;
30 import java.util.Set;
31 import java.util.concurrent.ConcurrentHashMap;
32
33 import javax.naming.Binding;
34 import javax.naming.NamingException;
35 import javax.naming.directory.DirContext;
36 import javax.servlet.RequestDispatcher;
37 import javax.servlet.Servlet;
38 import javax.servlet.ServletContext;
39 import javax.servlet.ServletContextAttributeEvent;
40 import javax.servlet.ServletContextAttributeListener;
41
42 import org.apache.catalina.Context;
43 import org.apache.catalina.Host;
44 import org.apache.catalina.Wrapper;
45 import org.apache.catalina.deploy.ApplicationParameter;
46 import org.apache.catalina.util.Enumerator;
47 import org.apache.catalina.util.ResourceSet;
48 import org.apache.catalina.util.ServerInfo;
49 import org.apache.catalina.util.StringManager;
50 import org.apache.naming.resources.DirContextURLStreamHandler;
51 import org.apache.naming.resources.Resource;
52 import org.apache.tomcat.util.buf.CharChunk;
53 import org.apache.tomcat.util.buf.MessageBytes;
54 import org.apache.tomcat.util.http.mapper.MappingData;
55
56
57 /**
58 * Standard implementation of <code>ServletContext</code> that represents
59 * a web application's execution environment. An instance of this class is
60 * associated with each instance of <code>StandardContext</code>.
61 *
62 * @author Craig R. McClanahan
63 * @author Remy Maucherat
64 * @version $Revision: 606606 $ $Date: 2007-12-23 21:09:23 +0100 (dim., 23 déc. 2007) $
65 */
66
67 public class ApplicationContext
68 implements ServletContext {
69
70 // ----------------------------------------------------------- Constructors
71
72
73 /**
74 * Construct a new instance of this class, associated with the specified
75 * Context instance.
76 *
77 * @param context The associated Context instance
78 */
79 public ApplicationContext(String basePath, StandardContext context) {
80 super();
81 this.context = context;
82 this.basePath = basePath;
83 }
84
85
86 // ----------------------------------------------------- Instance Variables
87
88
89 /**
90 * The context attributes for this context.
91 */
92 protected Map attributes = new ConcurrentHashMap();
93
94
95 /**
96 * List of read only attributes for this context.
97 */
98 private Map readOnlyAttributes = new ConcurrentHashMap();
99
100
101 /**
102 * The Context instance with which we are associated.
103 */
104 private StandardContext context = null;
105
106
107 /**
108 * Empty collection to serve as the basis for empty enumerations.
109 * <strong>DO NOT ADD ANY ELEMENTS TO THIS COLLECTION!</strong>
110 */
111 private static final ArrayList empty = new ArrayList();
112
113
114 /**
115 * The facade around this object.
116 */
117 private ServletContext facade = new ApplicationContextFacade(this);
118
119
120 /**
121 * The merged context initialization parameters for this Context.
122 */
123 private Map parameters = null;
124
125
126 /**
127 * The string manager for this package.
128 */
129 private static final StringManager sm =
130 StringManager.getManager(Constants.Package);
131
132
133 /**
134 * Base path.
135 */
136 private String basePath = null;
137
138
139 /**
140 * Thread local data used during request dispatch.
141 */
142 private ThreadLocal<DispatchData> dispatchData =
143 new ThreadLocal<DispatchData>();
144
145
146 // --------------------------------------------------------- Public Methods
147
148
149 /**
150 * Return the resources object that is mapped to a specified path.
151 * The path must begin with a "/" and is interpreted as relative to the
152 * current context root.
153 */
154 public DirContext getResources() {
155
156 return context.getResources();
157
158 }
159
160
161 // ------------------------------------------------- ServletContext Methods
162
163
164 /**
165 * Return the value of the specified context attribute, if any;
166 * otherwise return <code>null</code>.
167 *
168 * @param name Name of the context attribute to return
169 */
170 public Object getAttribute(String name) {
171
172 return (attributes.get(name));
173
174 }
175
176
177 /**
178 * Return an enumeration of the names of the context attributes
179 * associated with this context.
180 */
181 public Enumeration getAttributeNames() {
182
183 return new Enumerator(attributes.keySet(), true);
184
185 }
186
187
188 /**
189 * Return a <code>ServletContext</code> object that corresponds to a
190 * specified URI on the server. This method allows servlets to gain
191 * access to the context for various parts of the server, and as needed
192 * obtain <code>RequestDispatcher</code> objects or resources from the
193 * context. The given path must be absolute (beginning with a "/"),
194 * and is interpreted based on our virtual host's document root.
195 *
196 * @param uri Absolute URI of a resource on the server
197 */
198 public ServletContext getContext(String uri) {
199
200 // Validate the format of the specified argument
201 if ((uri == null) || (!uri.startsWith("/")))
202 return (null);
203
204 Context child = null;
205 try {
206 Host host = (Host) context.getParent();
207 String mapuri = uri;
208 while (true) {
209 child = (Context) host.findChild(mapuri);
210 if (child != null)
211 break;
212 int slash = mapuri.lastIndexOf('/');
213 if (slash < 0)
214 break;
215 mapuri = mapuri.substring(0, slash);
216 }
217 } catch (Throwable t) {
218 return (null);
219 }
220
221 if (child == null)
222 return (null);
223
224 if (context.getCrossContext()) {
225 // If crossContext is enabled, can always return the context
226 return child.getServletContext();
227 } else if (child == context) {
228 // Can still return the current context
229 return context.getServletContext();
230 } else {
231 // Nothing to return
232 return (null);
233 }
234 }
235
236
237 /**
238 * Return the main path associated with this context.
239 */
240 public String getContextPath() {
241 return context.getPath();
242 }
243
244
245 /**
246 * Return the value of the specified initialization parameter, or
247 * <code>null</code> if this parameter does not exist.
248 *
249 * @param name Name of the initialization parameter to retrieve
250 */
251 public String getInitParameter(final String name) {
252
253 mergeParameters();
254 return ((String) parameters.get(name));
255
256 }
257
258
259 /**
260 * Return the names of the context's initialization parameters, or an
261 * empty enumeration if the context has no initialization parameters.
262 */
263 public Enumeration getInitParameterNames() {
264
265 mergeParameters();
266 return (new Enumerator(parameters.keySet()));
267
268 }
269
270
271 /**
272 * Return the major version of the Java Servlet API that we implement.
273 */
274 public int getMajorVersion() {
275
276 return (Constants.MAJOR_VERSION);
277
278 }
279
280
281 /**
282 * Return the minor version of the Java Servlet API that we implement.
283 */
284 public int getMinorVersion() {
285
286 return (Constants.MINOR_VERSION);
287
288 }
289
290
291 /**
292 * Return the MIME type of the specified file, or <code>null</code> if
293 * the MIME type cannot be determined.
294 *
295 * @param file Filename for which to identify a MIME type
296 */
297 public String getMimeType(String file) {
298
299 if (file == null)
300 return (null);
301 int period = file.lastIndexOf(".");
302 if (period < 0)
303 return (null);
304 String extension = file.substring(period + 1);
305 if (extension.length() < 1)
306 return (null);
307 return (context.findMimeMapping(extension));
308
309 }
310
311
312 /**
313 * Return a <code>RequestDispatcher</code> object that acts as a
314 * wrapper for the named servlet.
315 *
316 * @param name Name of the servlet for which a dispatcher is requested
317 */
318 public RequestDispatcher getNamedDispatcher(String name) {
319
320 // Validate the name argument
321 if (name == null)
322 return (null);
323
324 // Create and return a corresponding request dispatcher
325 Wrapper wrapper = (Wrapper) context.findChild(name);
326 if (wrapper == null)
327 return (null);
328
329 return new ApplicationDispatcher(wrapper, null, null, null, null, name);
330
331 }
332
333
334 /**
335 * Return the real path for a given virtual path, if possible; otherwise
336 * return <code>null</code>.
337 *
338 * @param path The path to the desired resource
339 */
340 public String getRealPath(String path) {
341
342 if (!context.isFilesystemBased())
343 return null;
344
345 if (path == null) {
346 return null;
347 }
348
349 File file = new File(basePath, path);
350 return (file.getAbsolutePath());
351
352 }
353
354
355 /**
356 * Return a <code>RequestDispatcher</code> instance that acts as a
357 * wrapper for the resource at the given path. The path must begin
358 * with a "/" and is interpreted as relative to the current context root.
359 *
360 * @param path The path to the desired resource.
361 */
362 public RequestDispatcher getRequestDispatcher(String path) {
363
364 // Validate the path argument
365 if (path == null)
366 return (null);
367 if (!path.startsWith("/"))
368 throw new IllegalArgumentException
369 (sm.getString
370 ("applicationContext.requestDispatcher.iae", path));
371 path = normalize(path);
372 if (path == null)
373 return (null);
374
375 // Use the thread local URI and mapping data
376 DispatchData dd = dispatchData.get();
377 if (dd == null) {
378 dd = new DispatchData();
379 dispatchData.set(dd);
380 }
381
382 MessageBytes uriMB = dd.uriMB;
383 uriMB.recycle();
384
385 // Get query string
386 String queryString = null;
387 int pos = path.indexOf('?');
388 if (pos >= 0) {
389 queryString = path.substring(pos + 1);
390 } else {
391 pos = path.length();
392 }
393
394 // Use the thread local mapping data
395 MappingData mappingData = dd.mappingData;
396
397 // Map the URI
398 CharChunk uriCC = uriMB.getCharChunk();
399 try {
400 uriCC.append(context.getPath(), 0, context.getPath().length());
401 /*
402 * Ignore any trailing path params (separated by ';') for mapping
403 * purposes
404 */
405 int semicolon = path.indexOf(';');
406 if (pos >= 0 && semicolon > pos) {
407 semicolon = -1;
408 }
409 uriCC.append(path, 0, semicolon > 0 ? semicolon : pos);
410 context.getMapper().map(uriMB, mappingData);
411 if (mappingData.wrapper == null) {
412 return (null);
413 }
414 /*
415 * Append any trailing path params (separated by ';') that were
416 * ignored for mapping purposes, so that they're reflected in the
417 * RequestDispatcher's requestURI
418 */
419 if (semicolon > 0) {
420 uriCC.append(path, semicolon, pos - semicolon);
421 }
422 } catch (Exception e) {
423 // Should never happen
424 log(sm.getString("applicationContext.mapping.error"), e);
425 return (null);
426 }
427
428 Wrapper wrapper = (Wrapper) mappingData.wrapper;
429 String wrapperPath = mappingData.wrapperPath.toString();
430 String pathInfo = mappingData.pathInfo.toString();
431
432 mappingData.recycle();
433
434 // Construct a RequestDispatcher to process this request
435 return new ApplicationDispatcher
436 (wrapper, uriCC.toString(), wrapperPath, pathInfo,
437 queryString, null);
438
439 }
440
441
442
443 /**
444 * Return the URL to the resource that is mapped to a specified path.
445 * The path must begin with a "/" and is interpreted as relative to the
446 * current context root.
447 *
448 * @param path The path to the desired resource
449 *
450 * @exception MalformedURLException if the path is not given
451 * in the correct form
452 */
453 public URL getResource(String path)
454 throws MalformedURLException {
455
456 if (path == null || !path.startsWith("/")) {
457 throw new MalformedURLException(sm.getString("applicationContext.requestDispatcher.iae", path));
458 }
459
460 path = normalize(path);
461 if (path == null)
462 return (null);
463
464 String libPath = "/WEB-INF/lib/";
465 if ((path.startsWith(libPath)) && (path.endsWith(".jar"))) {
466 File jarFile = null;
467 if (context.isFilesystemBased()) {
468 jarFile = new File(basePath, path);
469 } else {
470 jarFile = new File(context.getWorkPath(), path);
471 }
472 if (jarFile.exists()) {
473 return jarFile.toURL();
474 } else {
475 return null;
476 }
477 } else {
478
479 DirContext resources = context.getResources();
480 if (resources != null) {
481 String fullPath = context.getName() + path;
482 String hostName = context.getParent().getName();
483 try {
484 resources.lookup(path);
485 return new URL
486 ("jndi", "", 0, getJNDIUri(hostName, fullPath),
487 new DirContextURLStreamHandler(resources));
488 } catch (Exception e) {
489 // Ignore
490 }
491 }
492 }
493
494 return (null);
495
496 }
497
498
499 /**
500 * Return the requested resource as an <code>InputStream</code>. The
501 * path must be specified according to the rules described under
502 * <code>getResource</code>. If no such resource can be identified,
503 * return <code>null</code>.
504 *
505 * @param path The path to the desired resource.
506 */
507 public InputStream getResourceAsStream(String path) {
508
509 path = normalize(path);
510 if (path == null || !path.startsWith("/"))
511 return (null);
512
513 DirContext resources = context.getResources();
514 if (resources != null) {
515 try {
516 Object resource = resources.lookup(path);
517 if (resource instanceof Resource)
518 return (((Resource) resource).streamContent());
519 } catch (Exception e) {
520 }
521 }
522 return (null);
523
524 }
525
526
527 /**
528 * Return a Set containing the resource paths of resources member of the
529 * specified collection. Each path will be a String starting with
530 * a "/" character. The returned set is immutable.
531 *
532 * @param path Collection path
533 */
534 public Set getResourcePaths(String path) {
535
536 // Validate the path argument
537 if (path == null) {
538 return null;
539 }
540 if (!path.startsWith("/")) {
541 throw new IllegalArgumentException
542 (sm.getString("applicationContext.resourcePaths.iae", path));
543 }
544
545 path = normalize(path);
546 if (path == null)
547 return (null);
548
549 DirContext resources = context.getResources();
550 if (resources != null) {
551 return (getResourcePathsInternal(resources, path));
552 }
553 return (null);
554
555 }
556
557
558 /**
559 * Internal implementation of getResourcesPath() logic.
560 *
561 * @param resources Directory context to search
562 * @param path Collection path
563 */
564 private Set getResourcePathsInternal(DirContext resources, String path) {
565
566 ResourceSet set = new ResourceSet();
567 try {
568 listCollectionPaths(set, resources, path);
569 } catch (NamingException e) {
570 return (null);
571 }
572 set.setLocked(true);
573 return (set);
574
575 }
576
577
578 /**
579 * Return the name and version of the servlet container.
580 */
581 public String getServerInfo() {
582
583 return (ServerInfo.getServerInfo());
584
585 }
586
587
588 /**
589 * @deprecated As of Java Servlet API 2.1, with no direct replacement.
590 */
591 public Servlet getServlet(String name) {
592
593 return (null);
594
595 }
596
597
598 /**
599 * Return the display name of this web application.
600 */
601 public String getServletContextName() {
602
603 return (context.getDisplayName());
604
605 }
606
607
608 /**
609 * @deprecated As of Java Servlet API 2.1, with no direct replacement.
610 */
611 public Enumeration getServletNames() {
612 return (new Enumerator(empty));
613 }
614
615
616 /**
617 * @deprecated As of Java Servlet API 2.1, with no direct replacement.
618 */
619 public Enumeration getServlets() {
620 return (new Enumerator(empty));
621 }
622
623
624 /**
625 * Writes the specified message to a servlet log file.
626 *
627 * @param message Message to be written
628 */
629 public void log(String message) {
630
631 context.getLogger().info(message);
632
633 }
634
635
636 /**
637 * Writes the specified exception and message to a servlet log file.
638 *
639 * @param exception Exception to be reported
640 * @param message Message to be written
641 *
642 * @deprecated As of Java Servlet API 2.1, use
643 * <code>log(String, Throwable)</code> instead
644 */
645 public void log(Exception exception, String message) {
646
647 context.getLogger().error(message, exception);
648
649 }
650
651
652 /**
653 * Writes the specified message and exception to a servlet log file.
654 *
655 * @param message Message to be written
656 * @param throwable Exception to be reported
657 */
658 public void log(String message, Throwable throwable) {
659
660 context.getLogger().error(message, throwable);
661
662 }
663
664
665 /**
666 * Remove the context attribute with the specified name, if any.
667 *
668 * @param name Name of the context attribute to be removed
669 */
670 public void removeAttribute(String name) {
671
672 Object value = null;
673 boolean found = false;
674
675 // Remove the specified attribute
676 // Check for read only attribute
677 if (readOnlyAttributes.containsKey(name))
678 return;
679 found = attributes.containsKey(name);
680 if (found) {
681 value = attributes.get(name);
682 attributes.remove(name);
683 } else {
684 return;
685 }
686
687 // Notify interested application event listeners
688 Object listeners[] = context.getApplicationEventListeners();
689 if ((listeners == null) || (listeners.length == 0))
690 return;
691 ServletContextAttributeEvent event =
692 new ServletContextAttributeEvent(context.getServletContext(),
693 name, value);
694 for (int i = 0; i < listeners.length; i++) {
695 if (!(listeners[i] instanceof ServletContextAttributeListener))
696 continue;
697 ServletContextAttributeListener listener =
698 (ServletContextAttributeListener) listeners[i];
699 try {
700 context.fireContainerEvent("beforeContextAttributeRemoved",
701 listener);
702 listener.attributeRemoved(event);
703 context.fireContainerEvent("afterContextAttributeRemoved",
704 listener);
705 } catch (Throwable t) {
706 context.fireContainerEvent("afterContextAttributeRemoved",
707 listener);
708 // FIXME - should we do anything besides log these?
709 log(sm.getString("applicationContext.attributeEvent"), t);
710 }
711 }
712
713 }
714
715
716 /**
717 * Bind the specified value with the specified context attribute name,
718 * replacing any existing value for that name.
719 *
720 * @param name Attribute name to be bound
721 * @param value New attribute value to be bound
722 */
723 public void setAttribute(String name, Object value) {
724
725 // Name cannot be null
726 if (name == null)
727 throw new IllegalArgumentException
728 (sm.getString("applicationContext.setAttribute.namenull"));
729
730 // Null value is the same as removeAttribute()
731 if (value == null) {
732 removeAttribute(name);
733 return;
734 }
735
736 Object oldValue = null;
737 boolean replaced = false;
738
739 // Add or replace the specified attribute
740 // Check for read only attribute
741 if (readOnlyAttributes.containsKey(name))
742 return;
743 oldValue = attributes.get(name);
744 if (oldValue != null)
745 replaced = true;
746 attributes.put(name, value);
747
748 // Notify interested application event listeners
749 Object listeners[] = context.getApplicationEventListeners();
750 if ((listeners == null) || (listeners.length == 0))
751 return;
752 ServletContextAttributeEvent event = null;
753 if (replaced)
754 event =
755 new ServletContextAttributeEvent(context.getServletContext(),
756 name, oldValue);
757 else
758 event =
759 new ServletContextAttributeEvent(context.getServletContext(),
760 name, value);
761
762 for (int i = 0; i < listeners.length; i++) {
763 if (!(listeners[i] instanceof ServletContextAttributeListener))
764 continue;
765 ServletContextAttributeListener listener =
766 (ServletContextAttributeListener) listeners[i];
767 try {
768 if (replaced) {
769 context.fireContainerEvent
770 ("beforeContextAttributeReplaced", listener);
771 listener.attributeReplaced(event);
772 context.fireContainerEvent("afterContextAttributeReplaced",
773 listener);
774 } else {
775 context.fireContainerEvent("beforeContextAttributeAdded",
776 listener);
777 listener.attributeAdded(event);
778 context.fireContainerEvent("afterContextAttributeAdded",
779 listener);
780 }
781 } catch (Throwable t) {
782 if (replaced)
783 context.fireContainerEvent("afterContextAttributeReplaced",
784 listener);
785 else
786 context.fireContainerEvent("afterContextAttributeAdded",
787 listener);
788 // FIXME - should we do anything besides log these?
789 log(sm.getString("applicationContext.attributeEvent"), t);
790 }
791 }
792
793 }
794
795
796 // -------------------------------------------------------- Package Methods
797 protected StandardContext getContext() {
798 return this.context;
799 }
800
801 protected Map getReadonlyAttributes() {
802 return this.readOnlyAttributes;
803 }
804 /**
805 * Clear all application-created attributes.
806 */
807 protected void clearAttributes() {
808
809 // Create list of attributes to be removed
810 ArrayList list = new ArrayList();
811 Iterator iter = attributes.keySet().iterator();
812 while (iter.hasNext()) {
813 list.add(iter.next());
814 }
815
816 // Remove application originated attributes
817 // (read only attributes will be left in place)
818 Iterator keys = list.iterator();
819 while (keys.hasNext()) {
820 String key = (String) keys.next();
821 removeAttribute(key);
822 }
823
824 }
825
826
827 /**
828 * Return the facade associated with this ApplicationContext.
829 */
830 protected ServletContext getFacade() {
831
832 return (this.facade);
833
834 }
835
836
837 /**
838 * Set an attribute as read only.
839 */
840 void setAttributeReadOnly(String name) {
841
842 if (attributes.containsKey(name))
843 readOnlyAttributes.put(name, name);
844
845 }
846
847
848 // -------------------------------------------------------- Private Methods
849
850
851 /**
852 * Return a context-relative path, beginning with a "/", that represents
853 * the canonical version of the specified path after ".." and "." elements
854 * are resolved out. If the specified path attempts to go outside the
855 * boundaries of the current context (i.e. too many ".." path elements
856 * are present), return <code>null</code> instead.
857 *
858 * @param path Path to be normalized
859 */
860 private String normalize(String path) {
861
862 if (path == null) {
863 return null;
864 }
865
866 String normalized = path;
867
868 // Normalize the slashes
869 if (normalized.indexOf('\\') >= 0)
870 normalized = normalized.replace('\\', '/');
871
872 // Resolve occurrences of "/../" in the normalized path
873 while (true) {
874 int index = normalized.indexOf("/../");
875 if (index < 0)
876 break;
877 if (index == 0)
878 return (null); // Trying to go outside our context
879 int index2 = normalized.lastIndexOf('/', index - 1);
880 normalized = normalized.substring(0, index2) +
881 normalized.substring(index + 3);
882 }
883
884 // Return the normalized path that we have completed
885 return (normalized);
886
887 }
888
889
890 /**
891 * Merge the context initialization parameters specified in the application
892 * deployment descriptor with the application parameters described in the
893 * server configuration, respecting the <code>override</code> property of
894 * the application parameters appropriately.
895 */
896 private void mergeParameters() {
897
898 if (parameters != null)
899 return;
900 Map results = new ConcurrentHashMap();
901 String names[] = context.findParameters();
902 for (int i = 0; i < names.length; i++)
903 results.put(names[i], context.findParameter(names[i]));
904 ApplicationParameter params[] =
905 context.findApplicationParameters();
906 for (int i = 0; i < params.length; i++) {
907 if (params[i].getOverride()) {
908 if (results.get(params[i].getName()) == null)
909 results.put(params[i].getName(), params[i].getValue());
910 } else {
911 results.put(params[i].getName(), params[i].getValue());
912 }
913 }
914 parameters = results;
915
916 }
917
918
919 /**
920 * List resource paths (recursively), and store all of them in the given
921 * Set.
922 */
923 private static void listCollectionPaths
924 (Set set, DirContext resources, String path)
925 throws NamingException {
926
927 Enumeration childPaths = resources.listBindings(path);
928 while (childPaths.hasMoreElements()) {
929 Binding binding = (Binding) childPaths.nextElement();
930 String name = binding.getName();
931 StringBuffer childPath = new StringBuffer(path);
932 if (!"/".equals(path) && !path.endsWith("/"))
933 childPath.append("/");
934 childPath.append(name);
935 Object object = binding.getObject();
936 if (object instanceof DirContext) {
937 childPath.append("/");
938 }
939 set.add(childPath.toString());
940 }
941
942 }
943
944
945 /**
946 * Get full path, based on the host name and the context path.
947 */
948 private static String getJNDIUri(String hostName, String path) {
949 if (!path.startsWith("/"))
950 return "/" + hostName + "/" + path;
951 else
952 return "/" + hostName + path;
953 }
954
955
956 /**
957 * Internal class used as thread-local storage when doing path
958 * mapping during dispatch.
959 */
960 private final class DispatchData {
961
962 public MessageBytes uriMB;
963 public MappingData mappingData;
964
965 public DispatchData() {
966 uriMB = MessageBytes.newInstance();
967 CharChunk uriCC = uriMB.getCharChunk();
968 uriCC.setLimit(-1);
969 mappingData = new MappingData();
970 }
971 }
972
973
974 }