View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.commons.resourcehandler;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.OutputStream;
24  import java.util.Locale;
25  import java.util.Map;
26  import java.util.MissingResourceException;
27  import java.util.ResourceBundle;
28  import java.util.logging.Level;
29  import java.util.logging.Logger;
30  import java.util.zip.GZIPOutputStream;
31  
32  import javax.faces.FacesException;
33  import javax.faces.application.ProjectStage;
34  import javax.faces.application.Resource;
35  import javax.faces.application.ResourceHandler;
36  import javax.faces.application.ResourceHandlerWrapper;
37  import javax.faces.application.ResourceWrapper;
38  import javax.faces.context.ExternalContext;
39  import javax.faces.context.FacesContext;
40  import javax.servlet.http.HttpServletResponse;
41  
42  import org.apache.myfaces.commons.resourcehandler.config.element.Library;
43  import org.apache.myfaces.commons.resourcehandler.resource.ResourceHandlerCache;
44  import org.apache.myfaces.commons.resourcehandler.resource.ResourceHandlerCache.ResourceValue;
45  import org.apache.myfaces.commons.resourcehandler.resource.ResourceLoader;
46  import org.apache.myfaces.commons.resourcehandler.resource.ResourceMeta;
47  import org.apache.myfaces.commons.util.ClassUtils;
48  import org.apache.myfaces.commons.util.ExternalContextUtils;
49  import org.apache.myfaces.commons.util.RequestType;
50  import org.apache.myfaces.commons.util.StringUtils;
51  
52  /**
53   * 
54   * @author Leonardo Uribe
55   *
56   */
57  public class ExtendedResourceHandlerImpl extends ResourceHandlerWrapper
58  {
59      private static final String IS_RESOURCE_REQUEST = "org.apache.myfaces.commons.IS_RESOURCE_REQUEST";
60      
61      private static final String RESOURCE_LOCALE = "org.apache.myfaces.commons.RESOURCE_LOCALE";
62  
63      private ExtendedDefaultResourceHandlerSupport _resourceHandlerSupport;
64  
65      private ResourceHandlerCache _resourceHandlerCache;
66  
67      //private static final Log log = LogFactory.getLog(ResourceHandlerImpl.class);
68      private static final Logger log = Logger.getLogger(ExtendedResourceHandlerImpl.class.getName());
69  
70      private static final int _BUFFER_SIZE = 2048;
71      
72      private ResourceHandler _delegate;
73      
74      //private volatile Boolean filterOn;
75  
76      public ExtendedResourceHandlerImpl(ResourceHandler delegate)
77      {
78          this._delegate = delegate;
79          //Eager initialization
80          _resourceHandlerSupport = new ExtendedDefaultResourceHandlerSupport();
81      }
82      
83      /*
84      @Override
85      public Resource createResource(String resourceName)
86      {
87          return createResource(resourceName, null);
88      }*/
89      
90      @Override
91      public Resource createResource(String resourceName, String libraryName)
92      {
93          if (getResourceHandlerSupport().getMyFacesResourcesConfig().getLibrary(libraryName) != null)
94          {
95              return createResource(resourceName, libraryName, null);
96          }
97          else
98          {
99              return super.createResource(resourceName, libraryName);
100         }
101     }
102 
103     @Override
104     public Resource createResource(String resourceName, String libraryName,
105             String contentType)
106     {
107         if (getResourceHandlerSupport().getMyFacesResourcesConfig().getLibrary(libraryName) != null)
108         {
109             return defaultCreateResource(resourceName, libraryName, contentType);
110         }
111         else
112         {
113             return super.createResource(resourceName, libraryName, contentType);
114         }
115     }
116     
117     private Resource defaultCreateResource(String resourceName, String expectedLibraryName,
118             String contentType)
119     {
120         Resource resource = null;
121 
122         FacesContext facesContext = FacesContext.getCurrentInstance();
123 
124         if (contentType == null)
125         {
126             //Resolve contentType using ExternalContext.getMimeType
127             contentType = facesContext.getExternalContext().getMimeType(resourceName);
128         }
129         
130         String localePrefix = (String) facesContext.getAttributes().get(RESOURCE_LOCALE);
131         
132         if (localePrefix == null)
133         {
134             localePrefix = getLocalePrefixForLocateResource();
135         }
136         
137         //Calculate the real libraryName
138         String redirectedLibraryName = resolveLibraryName(expectedLibraryName);
139 
140         // check cache
141         if(getResourceLoaderCache().containsResource(resourceName, redirectedLibraryName, contentType, localePrefix))
142         {
143             ResourceValue resourceValue = getResourceLoaderCache().getResource(
144                     resourceName, redirectedLibraryName, contentType, localePrefix);
145             
146             //resource = new ResourceImpl(resourceValue.getResourceMeta(), resourceValue.getResourceLoader(),
147             //        getResourceHandlerSupport(), contentType);
148             resource = new ExtendedResourceImpl((ExtendedResourceMeta) resourceValue.getResourceMeta(), resourceValue.getResourceLoader(),
149                     getResourceHandlerSupport(), contentType, localePrefix, redirectedLibraryName.equals(expectedLibraryName) ? null : expectedLibraryName);
150         }
151         else
152         {
153             for (ResourceLoader loader : getResourceHandlerSupport().getResourceLoaders())
154             {
155                 ResourceMeta resourceMeta = deriveResourceMeta(loader, resourceName, redirectedLibraryName, localePrefix);
156     
157                 if (resourceMeta != null)
158                 {
159                     //resource = new ResourceImpl(resourceMeta, loader, getResourceHandlerSupport(), contentType);
160                     resource = new ExtendedResourceImpl((ExtendedResourceMeta) resourceMeta, loader, getResourceHandlerSupport(), contentType, localePrefix,
161                             redirectedLibraryName.equals(expectedLibraryName) ? null : expectedLibraryName);
162 
163                     // cache it
164                     getResourceLoaderCache().putResource(resourceName, redirectedLibraryName, contentType,
165                             localePrefix, resourceMeta, loader);
166                     break;
167                 }
168             }
169         }
170         
171         return resource;
172     }
173 
174     public String resolveLibraryName(String libraryName)
175     {
176         String finalLibraryName = libraryName;
177         Library library = null;
178         boolean resolved = false;
179         do 
180         {
181             library = getResourceHandlerSupport().getMyFacesResourcesConfig().getLibrary(finalLibraryName);
182             if (library != null)
183             {
184                 if (library.getRedirectName() != null && library.getRedirectName().length() > 0)
185                 {
186                     finalLibraryName = library.getRedirectName();
187                 }
188                 else
189                 {
190                     //No redirect, so this is the real instance
191                     resolved = true;
192                 }
193             }
194         } while (library != null && !resolved);
195         
196         return finalLibraryName;
197     }
198     
199     /**
200      * This method try to create a ResourceMeta for a specific resource
201      * loader. If no library, or resource is found, just return null,
202      * so the algorithm in createResource can continue checking with the 
203      * next registered ResourceLoader. 
204      */
205     protected ExtendedResourceMeta deriveResourceMeta(ResourceLoader resourceLoader,
206             String resourceName, String libraryName, String localePrefix)
207     {
208         String resourceVersion = null;
209         String libraryVersion = null;
210         ExtendedResourceMeta resourceId = null;
211         
212         //1. Try to locate resource in a localized path
213         if (localePrefix != null)
214         {
215             if (null != libraryName)
216             {
217                 String pathToLib = localePrefix + '/' + libraryName;
218                 libraryVersion = resourceLoader.getLibraryVersion(pathToLib);
219 
220                 if (null != libraryVersion)
221                 {
222                     String pathToResource = localePrefix + '/'
223                             + libraryName + '/' + libraryVersion + '/'
224                             + resourceName;
225                     resourceVersion = resourceLoader
226                             .getResourceVersion(pathToResource);
227                 }
228                 else
229                 {
230                     String pathToResource = localePrefix + '/'
231                             + libraryName + '/' + resourceName;
232                     resourceVersion = resourceLoader
233                             .getResourceVersion(pathToResource);
234                 }
235 
236                 if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
237                 {
238                     resourceId = (ExtendedResourceMeta) resourceLoader.createResourceMeta(localePrefix, libraryName,
239                             libraryVersion, resourceName, resourceVersion);
240                 }
241             }
242             else
243             {
244                 resourceVersion = resourceLoader
245                         .getResourceVersion(localePrefix + '/'+ resourceName);
246                 if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
247                 {               
248                     resourceId = (ExtendedResourceMeta) resourceLoader.createResourceMeta(localePrefix, null, null,
249                             resourceName, resourceVersion);
250                 }
251             }
252 
253             if (resourceId != null)
254             {
255                 if (!resourceLoader.resourceExists(resourceId))
256                 {
257                     resourceId = null;
258                 }
259             }            
260         }
261         
262         //2. Try to localize resource in a non localized path
263         if (resourceId == null)
264         {
265             if (null != libraryName)
266             {
267                 libraryVersion = resourceLoader.getLibraryVersion(libraryName);
268 
269                 if (null != libraryVersion)
270                 {
271                     String pathToResource = (libraryName + '/' + libraryVersion
272                             + '/' + resourceName);
273                     resourceVersion = resourceLoader
274                             .getResourceVersion(pathToResource);
275                 }
276                 else
277                 {
278                     String pathToResource = (libraryName + '/'
279                             + resourceName);
280                     resourceVersion = resourceLoader
281                             .getResourceVersion(pathToResource);
282                 }
283 
284                 if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
285                 {               
286                     resourceId = (ExtendedResourceMeta) resourceLoader.createResourceMeta(null, libraryName,
287                             libraryVersion, resourceName, resourceVersion);
288                 }
289             }
290             else
291             {
292                 resourceVersion = resourceLoader
293                         .getResourceVersion(resourceName);
294                 
295                 if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
296                 {               
297                     resourceId = (ExtendedResourceMeta) resourceLoader.createResourceMeta(null, null, null,
298                             resourceName, resourceVersion);
299                 }
300             }
301 
302             if (resourceId != null)
303             {
304                 if (!resourceLoader.resourceExists(resourceId))
305                 {
306                     resourceId = null;
307                 }
308             }            
309         }
310         
311         return resourceId;
312     }
313 
314     /**
315      *  Handle the resource request, writing in the output. 
316      *  
317      *  This method implements an algorithm semantically identical to 
318      *  the one described on the javadoc of ResourceHandler.handleResourceRequest 
319      */
320     @Override
321     public void handleResourceRequest(FacesContext facesContext) throws IOException
322     {
323         // Only if filter is on
324         //if (!isFilterOn())
325         //{
326         //    super.handleResourceRequest(facesContext);
327         //}
328         
329         // And this is a request handled from the filter first! 
330         //if (!facesContext.getAttributes().containsKey(ResourceHandlerFilter.RESOURCE_HANDLER_FILTER_REQUEST))
331         //{
332         //    super.handleResourceRequest(facesContext);
333         //}
334         
335         try
336         {
337             String resourceBasePath = getResourceHandlerSupport()
338                     .calculateResourceBasePath(facesContext);
339     
340             if (resourceBasePath == null)
341             {
342                 // No base name could be calculated, so no further
343                 //advance could be done here. HttpServletResponse.SC_NOT_FOUND
344                 //cannot be returned since we cannot extract the 
345                 //resource base name
346                 super.handleResourceRequest(facesContext);
347                 return;
348             }
349     
350             // We neet to get an instance of HttpServletResponse, but sometimes
351             // the response object is wrapped by several instances of 
352             // ServletResponseWrapper (like ResponseSwitch).
353             // Since we are handling a resource, we can expect to get an 
354             // HttpServletResponse.
355             if (!RequestType.SERVLET.equals(ExternalContextUtils.getRequestType(facesContext.getExternalContext())))
356             {
357                 throw new IllegalStateException("Could not obtain an instance of HttpServletResponse.");
358             }
359             Object response = facesContext.getExternalContext().getResponse();
360             HttpServletResponse httpServletResponse = (HttpServletResponse) response;
361             if (httpServletResponse == null)
362             {
363                 throw new IllegalStateException("Could not obtain an instance of HttpServletResponse.");
364             }
365     
366             if (isResourceIdentifierExcluded(facesContext, resourceBasePath))
367             {
368                 httpServletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
369                 return;
370             }
371 
372             String resourceName = null;
373             String libraryName = null;
374             String requestedLocalePrefix = null;
375             if (resourceBasePath.startsWith(getResourceHandlerSupport().getResourceIdentifier()))
376             {
377                 //resourceName = resourceBasePath
378                 //        .substring(ResourceHandler.RESOURCE_IDENTIFIER.length() + 1);
379                 resourceName = resourceBasePath
380                         .substring(getResourceHandlerSupport().getResourceIdentifier().length());
381                 
382                 if (resourceName.startsWith("/$"))
383                 {
384                     //Extract locale prefix, libraryName and resourceName
385                     int from = 3;
386                     int to = resourceName.indexOf('/',3);
387                     requestedLocalePrefix = resourceName.substring(from,to);
388                     from = to+1;
389                     to = resourceName.indexOf('/', from);
390                     libraryName = resourceName.substring(from, to);
391                     resourceName = resourceName.substring(to+1);
392                 }
393                 else
394                 {
395                     //No special identifier used, delegate to default algorithm
396                     resourceName = null;
397                     //Try to get the library name using the standard form
398                     //libraryName = facesContext.getExternalContext()
399                     //    .getRequestParameterMap().get("ln");
400                 }
401             }
402             else
403             {
404                 //Does not have the conditions for be a resource call, let the base one
405                 //return not found
406                 super.handleResourceRequest(facesContext);
407                 return;
408             }
409             
410             //Only resources with resourceName and advanced libraryName are handled by this handler
411             if (resourceName == null)
412             {
413                 super.handleResourceRequest(facesContext);
414                 return;
415             }
416             else if (libraryName == null)
417             {
418                 super.handleResourceRequest(facesContext);
419                 return;
420             }
421             else if (libraryName != null && getResourceHandlerSupport().getMyFacesResourcesConfig().getLibrary(libraryName) == null)
422             {
423                 super.handleResourceRequest(facesContext);
424                 return;
425             }
426     
427             if (requestedLocalePrefix != null)
428             {
429                 facesContext.getAttributes().put(RESOURCE_LOCALE, requestedLocalePrefix);
430             }
431             
432             Resource resource = null;
433             //if (libraryName != null)
434             //{
435                 //log.info("libraryName=" + libraryName);
436                 resource = facesContext.getApplication().getResourceHandler().createResource(resourceName, libraryName);
437             //}
438             //else
439             //{
440             //    resource = facesContext.getApplication().getResourceHandler().createResource(resourceName);
441             //}
442     
443             if (resource == null)
444             {
445                 httpServletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
446                 return;
447             }
448     
449             if (!resource.userAgentNeedsUpdate(facesContext))
450             {
451                 httpServletResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
452                 return;
453             }
454     
455             httpServletResponse.setContentType(_getContentType(resource, facesContext.getExternalContext()));
456     
457             Map<String, String> headers = resource.getResponseHeaders();
458     
459             for (Map.Entry<String, String> entry : headers.entrySet())
460             {
461                 httpServletResponse.setHeader(entry.getKey(), entry.getValue());
462             }
463             
464             //serve up the bytes (taken from trinidad ResourceServlet)
465             try
466             {
467                 // we should serve a compressed version of the resource, if
468                 //   - ProjectStage != Development
469                 //   - a compressed version is available (created in constructor)
470                 //   - the user agent supports compresssion
471                 // and if there is no caching on disk, do compression here!
472                 if (!facesContext.isProjectStage(ProjectStage.Development) &&
473                     getResourceHandlerSupport().isGzipResourcesEnabled() && 
474                     !getResourceHandlerSupport().isCacheDiskGzipResources() && 
475                     getResourceHandlerSupport().userAgentSupportsCompression(facesContext) && 
476                     getResourceHandlerSupport().isCompressable(resource))
477                 {
478                     InputStream in = resource.getInputStream();
479                     OutputStream out = new GZIPOutputStream(httpServletResponse.getOutputStream(), _BUFFER_SIZE);
480                     byte[] buffer = new byte[_BUFFER_SIZE];
481         
482                     try
483                     {
484                         int count = pipeBytes(in, out, buffer);
485                         //set the content lenght
486                         httpServletResponse.setContentLength(count);
487                     }
488                     finally
489                     {
490                         try
491                         {
492                             in.close();
493                         }
494                         finally
495                         {
496                             out.close();
497                         }
498                     }
499                 }
500                 else
501                 {
502                     InputStream in = resource.getInputStream();
503                     OutputStream out = httpServletResponse.getOutputStream();
504                     byte[] buffer = new byte[_BUFFER_SIZE];
505         
506                     try
507                     {
508                         int count = pipeBytes(in, out, buffer);
509                         //set the content lenght
510                         httpServletResponse.setContentLength(count);
511                     }
512                     finally
513                     {
514                         try
515                         {
516                             in.close();
517                         }
518                         finally
519                         {
520                             out.close();
521                         }
522                     }
523                 }
524             }
525             catch (IOException e)
526             {
527                 //TODO: Log using a localized message (which one?)
528                 if (log.isLoggable(Level.SEVERE))
529                     log.severe("Error trying to load resource " + resourceName
530                             + " with library " + libraryName + " :"
531                             + e.getMessage());
532                 httpServletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
533             }
534         }
535         catch (Throwable ex)
536         {
537             // handle the Throwable accordingly. Maybe generate an error page.
538             // FIXME we are creating a html error page for a non html request here
539             // shouln't we do something better? -=Jakob Korherr=-
540             throw new FacesException(ex);
541             //ErrorPageWriter.handleThrowable(facesContext, ex);
542         }
543     }
544 
545     /**
546      * Reads the specified input stream into the provided byte array storage and
547      * writes it to the output stream.
548      */
549     private static int pipeBytes(InputStream in, OutputStream out, byte[] buffer)
550             throws IOException
551     {
552         int count = 0;
553         int length;
554 
555         while ((length = (in.read(buffer))) >= 0)
556         {
557             out.write(buffer, 0, length);
558             count += length;
559         }
560         return count;
561     }
562 
563     @Override
564     public boolean isResourceRequest(FacesContext facesContext)
565     {
566         // Since this method could be called many times we save it
567         //on request map so the first time is calculated it remains
568         //alive until the end of the request
569         Boolean value = (Boolean) facesContext.getAttributes().get(IS_RESOURCE_REQUEST);
570 
571         if (value != null && value)
572         {
573             //return the saved value
574             return value;
575         }
576         else
577         {
578             String resourceBasePath = getResourceHandlerSupport()
579                     .calculateResourceBasePath(facesContext);
580 
581             if (resourceBasePath != null
582                     && resourceBasePath.startsWith(getResourceHandlerSupport().getResourceIdentifier()))
583             {
584                 facesContext.getAttributes().put(IS_RESOURCE_REQUEST, Boolean.TRUE);
585                 return true;
586             }
587             else
588             {
589                 value = super.isResourceRequest(facesContext); 
590                 facesContext.getAttributes().put(IS_RESOURCE_REQUEST, value);
591                 return value;
592             }
593         }
594     }
595 
596     protected String getLocalePrefixForLocateResource()
597     {
598         String localePrefix = null;
599         FacesContext context = FacesContext.getCurrentInstance();
600 
601         String bundleName = context.getApplication().getMessageBundle();
602 
603         if (null != bundleName)
604         {
605             Locale locale = context.getApplication().getViewHandler()
606                     .calculateLocale(context);
607 
608             ResourceBundle bundle = ResourceBundle
609                     .getBundle(bundleName, locale, ClassUtils.getContextClassLoader());
610 
611             if (bundle != null)
612             {
613                 try
614                 {
615                     localePrefix = bundle.getString(ResourceHandler.LOCALE_PREFIX);
616                 }
617                 catch (MissingResourceException e)
618                 {
619                     // Ignore it and return null
620                 }
621             }
622         }
623         return localePrefix;
624     }
625 
626     protected boolean isResourceIdentifierExcluded(FacesContext context,
627             String resourceIdentifier)
628     {
629         String value = context.getExternalContext().getInitParameter(
630                 RESOURCE_EXCLUDES_PARAM_NAME);
631         if (value == null)
632         {
633             value = RESOURCE_EXCLUDES_DEFAULT_VALUE;
634         }
635         //TODO: optimize this code
636         String[] extensions = StringUtils.splitShortString(value, ' ');
637         for (int i = 0; i < extensions.length; i++)
638         {
639             if (resourceIdentifier.endsWith(extensions[i]))
640             {
641                 return true;
642             }
643         }
644         return false;
645     }
646 
647     /**
648      * Check if a library exists or not. This is done delegating
649      * to each ResourceLoader used, because each one has a different
650      * prefix and way to load resources.
651      * 
652      */
653     @Override
654     public boolean libraryExists(String libraryName)
655     {
656         if (getResourceHandlerSupport().getMyFacesResourcesConfig().getLibrary(libraryName) != null)
657         {
658             String localePrefix = getLocalePrefixForLocateResource();
659     
660             String pathToLib = null;
661             
662             if (localePrefix != null)
663             {
664                 //Check with locale
665                 pathToLib = localePrefix + '/' + libraryName;
666                 
667                 for (ResourceLoader loader : getResourceHandlerSupport()
668                         .getResourceLoaders())
669                 {
670                     if (loader.libraryExists(pathToLib))
671                     {
672                         return true;
673                     }
674                 }            
675             }
676     
677             //Check without locale
678             for (ResourceLoader loader : getResourceHandlerSupport()
679                     .getResourceLoaders())
680             {
681                 if (loader.libraryExists(libraryName))
682                 {
683                     return true;
684                 }
685             }
686         }
687         else
688         {
689             super.libraryExists(libraryName);
690         }
691 
692         return false;
693     }
694 
695     /**
696      * @param resourceHandlerSupport
697      *            the resourceHandlerSupport to set
698      */
699     public void setResourceHandlerSupport(
700             ExtendedDefaultResourceHandlerSupport resourceHandlerSupport)
701     {
702         _resourceHandlerSupport = resourceHandlerSupport;
703     }
704 
705     /**
706      * @return the resourceHandlerSupport
707      */
708     protected ExtendedDefaultResourceHandlerSupport getResourceHandlerSupport()
709     {
710         return _resourceHandlerSupport;
711     }
712 
713     private ResourceHandlerCache getResourceLoaderCache()
714     {
715         if (_resourceHandlerCache == null)
716             _resourceHandlerCache = new ResourceHandlerCache();
717         return _resourceHandlerCache;
718     }
719 
720     private String _getContentType(Resource resource, ExternalContext externalContext)
721     {
722         String contentType = resource.getContentType();
723 
724         // the resource does not provide a content-type --> determine it via mime-type
725         if (contentType == null || contentType.length() == 0)
726         {
727             String resourceName = getWrappedResourceName(resource);
728 
729             if (resourceName != null)
730             {
731                 contentType = externalContext.getMimeType(resourceName);
732             }
733         }
734 
735         return contentType;
736     }
737 
738     /**
739      * Recursively unwarp the resource until we find the real resourceName
740      * This is needed because the JSF2 specced ResourceWrapper doesn't override
741      * the getResourceName() method :(
742      * @param resource
743      * @return the first non-null resourceName or <code>null</code> if none set
744      */
745     private String getWrappedResourceName(Resource resource)
746     {
747         String resourceName = resource.getResourceName();
748         if (resourceName != null)
749         {
750             return resourceName;
751         }
752 
753         if (resource instanceof ResourceWrapper)
754         {
755             return getWrappedResourceName(((ResourceWrapper) resource).getWrapped());
756         }
757 
758         return null;
759     }
760 
761     @Override
762     public ResourceHandler getWrapped()
763     {
764         return _delegate;
765     }
766 }