Package org.onebusaway.presentation.impl.resources

Source Code of org.onebusaway.presentation.impl.resources.ResourceServiceImpl$LocaleProviderImpl

/**
* Copyright (C) 2011 Brian Ferris <bdferris@onebusaway.org>
*
* 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 org.onebusaway.presentation.impl.resources;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.text.DateFormatSymbols;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.PostConstruct;
import javax.servlet.ServletContext;

import org.json.JSONObject;
import org.onebusaway.presentation.impl.ServletLibrary;
import org.onebusaway.presentation.services.resources.Resource;
import org.onebusaway.presentation.services.resources.ResourceService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.stereotype.Component;

import com.opensymphony.xwork2.LocaleProvider;
import com.opensymphony.xwork2.TextProvider;
import com.opensymphony.xwork2.TextProviderFactory;

@Component
public class ResourceServiceImpl implements ResourceService {

  public static final String DEBUG_PROPERTY = ResourceServiceImpl.class.getName()
      + ".debug";

  private static final String PREFIX_CLASSPATH = "classpath:";

  private static final String PREFIX_COLLECTION = "collection:";

  private static final String PREFIX_COLLECTION_ENTRY = "collection-entry:";

  private static final String PREFIX_MESSAGES = "messages:";

  private static final String PREFIX_MESSAGES_DATE_LIBRARY = "DateLibrary";

  private static final String PREFIX_FILE = "file:";

  private static final Pattern _resourcePattern = Pattern.compile("^(.*)-\\w+\\.cache(\\.\\w+){0,1}$");

  private static Logger _log = LoggerFactory.getLogger(ResourceServiceImpl.class);

  private static TextProviderFactory _textProviderFactory = new TextProviderFactory();

  private ConcurrentMap<String, Resource> _resourceEntriesByResourcePath = new ConcurrentHashMap<String, Resource>();

  private ConcurrentMap<String, Resource> _resourceEntriesByExternalId = new ConcurrentHashMap<String, Resource>();

  private ConcurrentMap<String, List<String>> _resourcePathsById = new ConcurrentHashMap<String, List<String>>();

  /****
   *
   ****/

  private String _prefix;

  private String _pattern;

  private File _tempDir;

  private ServletContext _servletContext;

  private String _contextPath;

  private boolean _debug = false;

  public void setPrefix(String prefix) {
    _prefix = prefix;
  }

  public void setPattern(String pattern) {
    _pattern = pattern;
  }

  @Autowired
  public void setServletContext(ServletContext servletContext) {

    _servletContext = servletContext;
    _contextPath = ServletLibrary.getContextPath(_servletContext);

    File tmpDir = (File) _servletContext.getAttribute("javax.servlet.context.tempdir");
    if (tmpDir == null) {
      _log.warn("NO ServletContext TEMP DIR!");
      tmpDir = new File(System.getProperty("java.io.tmpdir"));
    }

    _tempDir = new File(tmpDir, "OneBusAwayResources");
    if (!_tempDir.exists())
      _tempDir.mkdirs();
  }

  @PostConstruct
  public void setup() {
    String value = System.getProperty(DEBUG_PROPERTY, "false");
    if (value != null && value.toLowerCase().equals("true"))
      _debug = true;
  }

  /****
   * {@link ResourceService} Interface
   ****/

  @Override
  public String getExternalUrlForResource(String resourcePath, Locale locale) {

    LocaleProvider localeProvider = new LocaleProviderImpl(locale);

    Resource resource = getResourceForPath(resourcePath, localeProvider, null);

    if (resource == null) {
      _log.warn("resource not found: " + resourcePath);
      return null;
    }

    if (_debug)
      refreshResource(resource);

    return resource.getExternalUrl();
  }

  @Override
  public String getExternalUrlForResources(List<String> resourcePaths,
      Locale locale) {

    return getExternalUrlForResources(null, resourcePaths, locale);
  }

  @Override
  public String getExternalUrlForResources(String resourceId,
      List<String> resourcePaths, Locale locale) {

    LocaleProvider localeProvider = new LocaleProviderImpl(locale);

    Resource resource = getResourceForPaths(resourceId, resourcePaths,
        localeProvider);

    if (resource == null) {
      _log.warn("resource not found: " + resourceId);
      return null;
    }

    if (_debug)
      refreshResource(resource);

    return resource.getExternalUrl();
  }

  @Override
  public Resource getLocalResourceForExternalId(String externalId, Locale locale) {

    Resource resource = _resourceEntriesByExternalId.get(externalId);

    if (resource == null) {

      /**
       * In case the resource has not been first requested as a resource(url)
       * first
       */
      String resourcePath = getExternalIdAsResourcePath(externalId);
      if (resourcePath != null) {

        LocaleProvider localeProvider = new LocaleProviderImpl(locale);

        /**
         * First we see if this is a resource identified by id
         */
        if (_resourcePathsById.containsKey(resourcePath)) {
          List<String> paths = _resourcePathsById.get(resourcePath);
          resource = getResourceForPaths(resourcePath, paths, localeProvider);
        }

        if (resource == null)
          resource = getResourceForPath(resourcePath, localeProvider, null);
      }
    }

    if (resource == null) {
      _log.warn("resource not found for external id: " + externalId);
      return null;
    }

    return resource;
  }

  /****
   * Private Methods
   ****/

  private String getResourcePathAsKey(String resourcePath,
      LocaleProvider localeProvider) {

    if (resourcePath.startsWith(PREFIX_MESSAGES)) {
      Locale locale = localeProvider.getLocale();
      return resourcePath + "-" + locale.toString();
    }

    return resourcePath;
  }

  private Resource getResourceForPath(String resourcePath,
      LocaleProvider localeProvider, URL sourceUrl) {

    String resourcePathKey = getResourcePathAsKey(resourcePath, localeProvider);

    Resource resource = _resourceEntriesByResourcePath.get(resourcePathKey);

    if (resource == null) {

      resource = createResourceForPath(resourcePath, sourceUrl, localeProvider);

      if (resource == null)
        return null;

      Resource existingResource = _resourceEntriesByResourcePath.putIfAbsent(
          resourcePathKey, resource);

      if (existingResource != null)
        return existingResource;
    }

    return resource;
  }

  private ResourceEntry createResourceForPath(String resourcePath,
      URL sourceUrl, LocaleProvider localeProvider) {

    if (sourceUrl == null)
      sourceUrl = getResourceAsSourceUrl(resourcePath, localeProvider);

    /**
     * If we can't find a source URL, then we can't create an entry
     */
    if (sourceUrl == null) {
      return null;
    }

    File localFile = getBundleResourceAsLocalFile(resourcePath, sourceUrl);

    ResourceTransformationStrategy strategy = getResourceTransformationStrategyForResource(
        resourcePath, localeProvider);

    ResourceEntry resource = new ResourceEntry(resourcePath, sourceUrl,
        localFile, strategy);
    generateLocalResourceAndExternalUrl(resource);
    return resource;
  }

  private URL getResourceAsSourceUrl(String resourceName,
      LocaleProvider localeProvider) {

    if (resourceName.startsWith(PREFIX_COLLECTION)) {
      resourceName = resourceName.substring(PREFIX_COLLECTION.length());
      return getCollectionResourceAsSourceUrl(resourceName, localeProvider);
    }

    if (resourceName.startsWith(PREFIX_MESSAGES)) {
      resourceName = resourceName.substring(PREFIX_MESSAGES.length());
      if (PREFIX_MESSAGES_DATE_LIBRARY.equals(resourceName)) {
        return getDateLibraryMessagesResourceAsSourceUrl(localeProvider);
      } else {
        return getMessagesResourceAsSourceUrl(resourceName, localeProvider);
      }
    }
   
    if (resourceName.startsWith(PREFIX_CLASSPATH)) {
      resourceName = resourceName.substring(PREFIX_CLASSPATH.length());
      ClassLoader loader = getClass().getClassLoader();
      URL resource = loader.getResource(resourceName);
      if (resource == null)
        _log.warn("unknown classpath resource: name=" + resourceName);
      return resource;
    }

    if (resourceName.startsWith(PREFIX_FILE)) {
      resourceName = resourceName.substring(PREFIX_FILE.length());
      File file = new File(resourceName);
      try {
        return file.toURI().toURL();
      } catch (MalformedURLException e) {
        throw new IllegalStateException("error requesting file url: "
            + resourceName);
      }
    }

    try {
      return _servletContext.getResource(resourceName);
    } catch (MalformedURLException ex) {
      throw new IllegalStateException("error requesting servlet url: "
          + resourceName, ex);
    }

  }

  private URL getCollectionResourceAsSourceUrl(String resourceName,
      LocaleProvider localeProvider) {

    int index = resourceName.indexOf('=');
    if (index == -1)
      throw new IllegalStateException("invalid resource collection specifier: "
          + resourceName);

    String collectionPrefix = resourceName.substring(0, index);
    String collectionResourcePath = resourceName.substring(index + 1);

    ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();

    Map<String, String> resourceMapping = new HashMap<String, String>();

    try {
      org.springframework.core.io.Resource[] resources = resolver.getResources(collectionResourcePath);

      for (org.springframework.core.io.Resource resource : resources) {
        URL url = resource.getURL();
        String name = getLocalUrlAsResourceName(url);
        Resource r = getResourceForPath(name, localeProvider, url);
        if (r != null) {
          String path = url.getPath();
          int sepIndex = path.lastIndexOf(File.separator);
          if (sepIndex != -1)
            path = path.substring(sepIndex + 1);
          resourceMapping.put(path, r.getExternalUrl());

          String alternateId = PREFIX_COLLECTION_ENTRY + collectionPrefix + ":"
              + path;
          _resourceEntriesByResourcePath.put(alternateId, r);
        }
      }

      File file = getOutputFile(PREFIX_COLLECTION + collectionPrefix + ".js");
      PrintWriter out = new PrintWriter(file);
      JSONObject obj = new JSONObject(resourceMapping);
      out.println("var OBA = window.OBA || {};");
      out.println("if(!OBA.Resources) { OBA.Resources = {}; }");
      out.println("OBA.Resources." + collectionPrefix + " = " + obj.toString()
          + ";");
      out.close();

      return getFileAsUrl(file);

    } catch (IOException ex) {
      throw new IllegalStateException("error loading resources", ex);
    }

  }

  private URL getMessagesResourceAsSourceUrl(String resourceName,
      LocaleProvider localeProvider) {

    int index = resourceName.indexOf('=');
    if (index == -1)
      throw new IllegalStateException("invalid resource messages specifier: "
          + resourceName);

    String messagesPrefix = resourceName.substring(0, index);
    String messagesResourceClassName = resourceName.substring(index + 1);
    Class<?> messagesResourceClass = null;

    try {
      messagesResourceClass = Class.forName(messagesResourceClassName);
    } catch (Throwable ex) {
      throw new IllegalStateException("error loading messages resource class "
          + messagesResourceClassName, ex);
    }

    TextProvider provider = _textProviderFactory.createInstance(
        messagesResourceClass, localeProvider);
    ResourceBundle bundle = provider.getTexts();

    Map<String, String> resourceMapping = new HashMap<String, String>();

    for (Enumeration<String> en = bundle.getKeys(); en.hasMoreElements();) {
      String key = en.nextElement();
      String value = bundle.getString(key);
      resourceMapping.put(key, value);
    }

    try {

      File file = getOutputFile(PREFIX_MESSAGES + messagesPrefix + ".js");
      PrintWriter out = new PrintWriter(file);
      JSONObject obj = new JSONObject(resourceMapping);
      out.println("var OBA = window.OBA || {};");
      out.println("if(!OBA.Resources) { OBA.Resources = {}; }");
      out.println("OBA.Resources." + messagesPrefix + " = " + obj.toString()
          + ";");
      out.close();

      return getFileAsUrl(file);

    } catch (IOException ex) {
      throw new IllegalStateException("error loading resources", ex);
    }
  }

  private URL getDateLibraryMessagesResourceAsSourceUrl(
      LocaleProvider localeProvider) {

    String messagesPrefix = PREFIX_MESSAGES_DATE_LIBRARY;
   
    DateFormatSymbols symbols = DateFormatSymbols.getInstance(localeProvider.getLocale());

    Map<String, Object> resourceMapping = new HashMap<String, Object>();
   
    resourceMapping.put("amPm", Arrays.asList(symbols.getAmPmStrings()));
    resourceMapping.put("eras", Arrays.asList(symbols.getEras()));
    resourceMapping.put("months", Arrays.asList(symbols.getMonths()));
    resourceMapping.put("shortMonths", Arrays.asList(symbols.getShortMonths()));
    resourceMapping.put("weekdays", Arrays.asList(symbols.getWeekdays()));
    resourceMapping.put("shortWeekdays", Arrays.asList(symbols.getShortWeekdays()));

    try {

      File file = getOutputFile(PREFIX_MESSAGES + messagesPrefix + ".js");
      PrintWriter out = new PrintWriter(file);
      JSONObject obj = new JSONObject(resourceMapping);
      out.println("var OBA = window.OBA || {};");
      out.println("if(!OBA.Resources) { OBA.Resources = {}; }");
      out.println("OBA.Resources." + messagesPrefix + " = " + obj.toString()
          + ";");
      out.close();

      return getFileAsUrl(file);

    } catch (IOException ex) {
      throw new IllegalStateException("error loading resources", ex);
    }
  }

  private File getBundleResourceAsLocalFile(String resourcePath, URL resourceUrl) {

    String protocol = resourceUrl.getProtocol();

    if ("file".equals(protocol))
      return new File(resourceUrl.getPath());

    if ("jar".equals(protocol)) {

      String path = resourceUrl.getPath();
      int index = path.indexOf('!');
      if (index != -1)
        path = path.substring(0, index);

      try {
        URL jarResourceUrl = new URL(path);
        File file = new File(jarResourceUrl.getPath());
        if (file.exists())
          return file;
      } catch (MalformedURLException e) {

      }
    }

    File path = new File(_servletContext.getRealPath(resourcePath));
    if (path.exists())
      return path;
    return null;
  }

  private ResourceTransformationStrategy getResourceTransformationStrategyForResource(
      String resourcePath, LocaleProvider localeProvider) {
    if (resourcePath.endsWith(".css"))
      return new CssResourceTransformationStrategy(localeProvider.getLocale());
    return new DefaultResourceTransformationStrategy();
  }

  private boolean refreshResource(Resource resource) {

    if (resource instanceof ResourceEntry) {
      return refreshResourceEntry((ResourceEntry) resource);
    } else if (resource instanceof ResourcesEntry) {
      return refreshResourcesEntry((ResourcesEntry) resource);
    }

    return false;
  }

  private boolean refreshResourceEntry(ResourceEntry resource) {

    /**
     * The refresh mechanism only applies when we can map a resource to a local
     * file
     */
    File sourceFile = resource.getSourceFile();

    if (sourceFile == null)
      return false;

    synchronized (resource) {

      long lastModifiedTime = resource.getLastModifiedTime();

      if (lastModifiedTime >= sourceFile.lastModified())
        return false;

      String existingId = resource.getExternalId();
      _resourceEntriesByExternalId.remove(existingId);

      generateLocalResourceAndExternalUrl(resource);

      return true;
    }
  }

  private void generateLocalResourceAndExternalUrl(ResourceEntry resource) {

    ResourceTransformationStrategy strategy = resource.getTransformationStrategy();

    URL localUrl = resource.getSourceResource();
    long contentLength = -1;

    if (strategy.requiresTransformation()) {
      File outputFile = getOutputFile(resource.getResourcePath());
      strategy.transformResource(this, localUrl, outputFile);
      localUrl = getFileAsUrl(outputFile);
      contentLength = outputFile.length();
    }

    if (contentLength == -1)
      contentLength = computeContentLengthForLocalUrl(localUrl);

    resource.setLocalUrl(localUrl);
    resource.setContentLength(contentLength);
    String key = getResourceKey(localUrl);

    String externalId = constructExternalId(resource.getResourcePath(), key);
    String externalUrl = constructExternalUrl(externalId);

    resource.setExternalId(externalId);
    resource.setExternalUrl(externalUrl);

    _resourceEntriesByExternalId.put(externalId, resource);
  }

  private long computeContentLengthForLocalUrl(URL localUrl) {

    InputStream in = null;

    try {
      in = localUrl.openStream();
      long contentLength = 0;
      byte[] buffer = new byte[1024];
      while (true) {
        int rc = in.read(buffer);
        if (rc == -1)
          return contentLength;
        contentLength += rc;
      }
    } catch (IOException ex) {
      throw new IllegalStateException("error reading local url " + localUrl, ex);
    } finally {
      if (in != null) {
        try {
          in.close();
        } catch (IOException ex) {
          _log.warn("error closing local url " + localUrl, ex);
        }
      }
    }
  }

  private String getResourceKey(URL localResource) {
    try {
      InputStream in = localResource.openStream();
      return ResourceSupport.getHash(in);
    } catch (IOException ex) {
      throw new IllegalStateException("error constructing key for resource: "
          + localResource, ex);
    }
  }

  private String constructExternalId(String resourcePath, String resourceKey) {
    String resourceExtension = null;

    int index = resourcePath.lastIndexOf('.');

    if (index != -1) {
      resourceExtension = resourcePath.substring(index + 1);
      resourcePath = resourcePath.substring(0, index);
    }

    StringBuilder b = new StringBuilder();

    b.append(resourcePath);

    if (!_debug) {
      b.append('-');
      b.append(resourceKey);
      b.append(".cache");
    }

    if (resourceExtension != null && resourceExtension.length() > 0)
      b.append('.').append(resourceExtension);

    String url = b.toString();
    return url;
  }

  private String getExternalIdAsResourcePath(String externalId) {

    if (_debug)
      return externalId;

    Matcher m = _resourcePattern.matcher(externalId);

    if (!m.matches())
      return null;

    String resourcePath = m.group(1);

    if (m.groupCount() > 1)
      resourcePath += m.group(2);

    return resourcePath;
  }

  private String constructExternalUrl(String externalId) {

    externalId = externalId.replaceAll("/", "%2f");
    externalId = externalId.replaceAll("\\*", "%2a");
    externalId = externalId.replaceAll(":", "%3a");

    String url = externalId;

    if (_pattern != null)
      url = _pattern.replaceAll("\\{\\}", url);

    if (_prefix != null)
      url = _prefix + url;

    if (_contextPath != null)
      url = _contextPath + url;

    return url;
  }

  /****
   * Multi-Resource Methods
   ****/

  private Resource getResourceForPaths(String resourceId,
      List<String> resourcePaths, LocaleProvider localeProvider) {

    if (resourceId == null) {
      resourceId = getResourceIdForResourcePaths(resourcePaths);
    } else {
      _resourcePathsById.putIfAbsent(resourceId, resourcePaths);
    }

    String resourceIdKey = getResourcePathAsKey(resourceId, localeProvider);

    Resource resource = _resourceEntriesByResourcePath.get(resourceIdKey);

    if (resource == null) {

      resource = createResourceForPaths(resourceId, resourcePaths,
          localeProvider);

      if (resource == null)
        return null;

      Resource existingResource = _resourceEntriesByResourcePath.putIfAbsent(
          resourceIdKey, resource);

      if (existingResource != null)
        return existingResource;
    }

    return resource;
  }

  private Resource createResourceForPaths(String resourceId,
      List<String> resourcePaths, LocaleProvider localeProvider) {

    List<Resource> resources = new ArrayList<Resource>(resourcePaths.size());

    String extension = null;

    for (String resourcePath : resourcePaths) {

      Resource resource = getResourceForPath(resourcePath, localeProvider, null);

      if (resource == null)
        return null;

      resources.add(resource);

      if (extension == null) {
        int index = resourcePath.lastIndexOf('.');
        if (index != -1)
          extension = resourcePath.substring(index + 1);
      }
    }

    if (extension != null)
      resourceId += "." + extension;

    ResourcesEntry entry = new ResourcesEntry(resourceId, resources);
    generateLocalResourcesAndExternalUrl(entry);
    return entry;
  }

  private String getResourceIdForResourcePaths(List<String> resourcePaths) {

    StringBuilder resourcePathIds = new StringBuilder();

    for (String resourcePath : resourcePaths) {

      if (resourcePathIds.length() > 0)
        resourcePathIds.append(File.pathSeparator);
      resourcePathIds.append(resourcePath);
    }

    return ResourceSupport.getHash(resourcePathIds.toString());
  }

  private boolean refreshResourcesEntry(ResourcesEntry resources) {

    boolean isRefreshed = false;

    synchronized (resources) {

      for (Resource resource : resources.getResources()) {
        if (refreshResource(resource))
          isRefreshed = true;
      }

      if (isRefreshed)
        generateLocalResourcesAndExternalUrl(resources);
    }

    return isRefreshed;
  }

  private void generateLocalResourcesAndExternalUrl(ResourcesEntry entry) {

    File outputFile = getOutputFile(entry.getResourceId());

    try {
      BufferedWriter out = new BufferedWriter(new FileWriter(outputFile));

      for (Resource resource : entry.getResources()) {

        synchronized (resource) {
          URL url = resource.getLocalUrl();
          BufferedReader reader = new BufferedReader(new InputStreamReader(
              url.openStream()));
          String line = null;

          while ((line = reader.readLine()) != null) {
            out.write(line);
            out.write('\n');
          }

          reader.close();
        }
      }

      out.close();
    } catch (IOException ex) {
      throw new IllegalStateException("error constructing resource", ex);
    }

    URL localUrl = getFileAsUrl(outputFile);

    entry.setLocalUrl(localUrl);
    entry.setContentLength(outputFile.length());

    String key = getResourceKey(localUrl);

    String externalId = constructExternalId(entry.getResourceId(), key);
    String externalUrl = constructExternalUrl(externalId);

    entry.setExternalId(externalId);
    entry.setExternalUrl(externalUrl);

    _resourceEntriesByExternalId.put(externalId, entry);
  }

  private String getLocalUrlAsResourceName(URL url) {
    String name = url.toExternalForm();
    int index = name.lastIndexOf('!');
    if (index != -1)
      name = name.substring(index + 1);
    return name;
  }

  private File getOutputFile(String path) {
    path.replace(':', File.separatorChar);
    File file = new File(_tempDir, path);
    File parent = file.getParentFile();
    if (!parent.exists())
      parent.mkdirs();
    return file;
  }

  private URL getFileAsUrl(File outputFile) {
    try {
      URI uri = outputFile.toURI();
      return uri.toURL();
    } catch (MalformedURLException ex) {
      throw new IllegalStateException("couldn't make url from file: "
          + outputFile, ex);
    }
  }

  private static class LocaleProviderImpl implements LocaleProvider {

    private final Locale _locale;

    public LocaleProviderImpl(Locale locale) {
      _locale = locale;
    }

    @Override
    public Locale getLocale() {
      return _locale;
    }
  }

}
TOP

Related Classes of org.onebusaway.presentation.impl.resources.ResourceServiceImpl$LocaleProviderImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.