content: /**
   * Copyright 2014 55 Minutes (http://www.55minutes.com)
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *    http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package fiftyfive.wicket.resource;
  
  import java.util.List;
  
  import org.apache.wicket.request.IRequestHandler;
  import org.apache.wicket.request.IRequestMapper;
  import org.apache.wicket.request.Request;
  import org.apache.wicket.request.Url;
  
  import org.apache.wicket.request.handler.resource.ResourceReferenceRequestHandler;
  
  import org.apache.wicket.request.mapper.AbstractMapper;
  import org.apache.wicket.request.mapper.parameter.IPageParametersEncoder;
  import org.apache.wicket.request.mapper.parameter.PageParameters;
  
  import org.apache.wicket.request.resource.IResource;
  import org.apache.wicket.request.resource.ResourceReference;
  import org.apache.wicket.request.resource.caching.IResourceCachingStrategy;
  import org.apache.wicket.request.resource.caching.IStaticCacheableResource;
  import org.apache.wicket.request.resource.caching.ResourceUrl;
  
  import org.apache.wicket.util.IProvider;
  import org.apache.wicket.util.resource.IResourceStream;
  import org.apache.wicket.util.time.Time;
  
  
  /**
   * Maps a single, static URL to a list of resources. When that URL is requested, respond by
   * merging all the resources together by delegating to {@link MergedResourceRequestHandler}.
   * 
   * @since 3.0
   */
  public class MergedResourceMapper extends AbstractMapper implements IRequestMapper
  {
      protected final String[] mountSegments;
      protected final List<ResourceReference> resources;
      protected final IPageParametersEncoder parametersEncoder;
      protected final IProvider<? extends IResourceCachingStrategy> cachingStrategy;
      
      public MergedResourceMapper(String path,
                                  List<ResourceReference> resources,
                                  IPageParametersEncoder parametersEncoder,
                                  IProvider<? extends IResourceCachingStrategy> cachingStrategy)
      {
          // TODO: validate arguments
          this.resources = resources;
          this.mountSegments = getMountSegments(path);
          this.parametersEncoder = parametersEncoder;
          this.cachingStrategy = cachingStrategy;
      }
      
      public int getCompatibilityScore(Request request)
      {
          return 0;
      }
      
      public IRequestHandler mapRequest(Request request)
      {
          PageParameters parameters = null;
          List<String> requestSegments = request.getUrl().getSegments();
          
          if(requestSegments.size() < this.mountSegments.length)
          {
              return null;
          }
          for(int i=0; i<this.mountSegments.length; i++)
          {
              String segment = requestSegments.get(i);
              if(i+1 == this.mountSegments.length)
              {
                  parameters = extractPageParameters(
                      request,
                      this.mountSegments.length,
                      this.parametersEncoder);
                  
                  ResourceUrl resourceUrl = new ResourceUrl(segment, parameters);
                  this.cachingStrategy.get().undecorateUrl(resourceUrl);
                  segment = resourceUrl.getFileName();
              }
              if(!segment.equals(this.mountSegments[i]))
              {
                  return null;
              }
          }
          
          return new MergedResourceRequestHandler(
              this.resources,
              parameters,
              getLastModifiedTime(getLastModifiedReference()));
      }
  
      public Url mapHandler(IRequestHandler requestHandler)
      {
          if(!(requestHandler instanceof ResourceReferenceRequestHandler)) return null;
  
          boolean matched = false;
          ResourceReferenceRequestHandler handler = (ResourceReferenceRequestHandler) requestHandler;
          for(ResourceReference ref : this.resources)
          {
              if(ref.equals(handler.getResourceReference()))
              {
                  matched = true;
                  break;
              }
          }
          
          if(!matched) return null;
  
          Url url = new Url();
          PageParameters parameters = new PageParameters(handler.getPageParameters());
          for(int i=0; i<this.mountSegments.length; i++)
          {
              String segment = mountSegments[i];
              if(i+1 == this.mountSegments.length)
              {
                  segment = applyCachingStrategy(segment, parameters);
              }
              url.getSegments().add(segment);
          }
          
          return encodePageParameters(url, parameters, this.parametersEncoder);
      }
      
      protected String applyCachingStrategy(String fileName, PageParameters parameters)
      {
          ResourceUrl resourceUrl = new ResourceUrl(fileName, parameters);
          ResourceReference lastMod = getLastModifiedReference();
          IResource res = lastMod.getResource();
          if(res instanceof IStaticCacheableResource)
          {
              this.cachingStrategy.get().decorateUrl(resourceUrl, (IStaticCacheableResource) res);
          }
          return resourceUrl.getFileName();
      }
      
      protected ResourceReference getLastModifiedReference()
      {
          ResourceReference lastModifiedRef = null;
          long lastMillis = -1;
          for(ResourceReference ref : this.resources)
          {
              Time refModified = getLastModifiedTime(ref);
              if(refModified != null && refModified.getMilliseconds() > lastMillis)
              {
                  lastMillis = refModified.getMilliseconds();
                  lastModifiedRef = ref;
              }
          }
          return lastModifiedRef != null ? lastModifiedRef : this.resources.get(0);
      }
      
      /**
       * De-reference the resource and open its stream to determine the last modified time.
       */
      protected Time getLastModifiedTime(ResourceReference ref)
      {
          Time modified = null;
          IResource res = ref.getResource();
          if(res instanceof IStaticCacheableResource)
          {
              IResourceStream stream = ((IStaticCacheableResource) res).getCacheableResourceStream();
              modified = stream.lastModifiedTime();
          }
          return modified;
      }
  }
  