Package com.liferay.faces.bridge.application

Source Code of com.liferay.faces.bridge.application.ResourceImpl

/**
* Copyright (c) 2000-2014 Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
package com.liferay.faces.bridge.application;

import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.net.URL;
import java.net.URLConnection;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;

import javax.faces.application.Resource;
import javax.faces.application.ResourceHandler;
import javax.faces.application.ResourceWrapper;
import javax.faces.context.FacesContext;

import com.liferay.faces.bridge.config.BridgeConfig;
import com.liferay.faces.bridge.container.PortletContainer;
import com.liferay.faces.bridge.context.BridgeContext;
import com.liferay.faces.util.application.ResourceConstants;
import com.liferay.faces.util.config.ConfiguredServletMapping;
import com.liferay.faces.util.lang.StringPool;
import com.liferay.faces.util.logging.Logger;
import com.liferay.faces.util.logging.LoggerFactory;


/**
* This class decorates the resource implementation from the JSF implementation.
*
* @author  Neil Griffin
*/
public class ResourceImpl extends ResourceWrapper implements Serializable {

  // serialVersionUID
  private static final long serialVersionUID = 827821821511052062L;

  // Logger
  private static final Logger logger = LoggerFactory.getLogger(ResourceImpl.class);

  // Private Constants
  private static final String HEADER_IF_MODIFIED_SINCE = "If-Modified-Since";
  private static final String HTTP_SPEC_DATE_PATTERN = "EEE, dd MMM yyyy HH:mm:ss zzz";

  // Private Constants: Resources that can't be cached.
  private static final String EXTENSION_FACES = ".faces";
  private static final String LIBRARY_NAME_JAVAX_FACES = "javax.faces";

  private static final ArrayList<String> NON_CACHED_RESOURCES = new ArrayList<String>(5);

  static {
    NON_CACHED_RESOURCES.add("jsf.js");
    NON_CACHED_RESOURCES.add("bridge.js");
    NON_CACHED_RESOURCES.add("bridge.uncompressed.js");
    NON_CACHED_RESOURCES.add("compat.js");
    NON_CACHED_RESOURCES.add("compat.uncompressed.js");
    NON_CACHED_RESOURCES.add("icefaces-compat.js");
    NON_CACHED_RESOURCES.add("icefaces-compat.uncompressed.js");
    NON_CACHED_RESOURCES.add("icepush.js");
    NON_CACHED_RESOURCES.add("icepush.uncompressed.js");
    NON_CACHED_RESOURCES.add("compat.js");
    NON_CACHED_RESOURCES.add("icefaces-compat.js");
  }

  // Private Data Members
  private Long lastModifiedInSeconds;
  private Resource wrappedResource;

  /**
   * This constructor is used by Mojarra via reflection during state saving.
   */
  public ResourceImpl() {
  }

  public ResourceImpl(Resource wrappedResource) {
    this.wrappedResource = wrappedResource;
  }

  /**
   * Since this method is not supplied by the {@link ResourceWrapper} class it has to be implemented here.
   */
  @Override
  public String toString() {
    return wrappedResource.toString();
  }

  /**
   * This method determines whether or not the browser (user agent) requesting this resource needs an update, which
   * can ultimately save bandwidth and be a big performance improvement.
   */
  @Override
  public boolean userAgentNeedsUpdate(FacesContext facesContext) {

    String resourceName = getResourceName();

    // If the wrapped resource indicates that it needs to be updated, then go ahead and trust that and return true.
    boolean needsUpdate = wrappedResource.userAgentNeedsUpdate(facesContext);

    // Otherwise, don't trust what the wrapped resource says! There is a portlet-lifecycle incompatibility in
    // Mojarra's ResourceImpl.userAgentNeedsUpdate() such that the instance of the ResourceInfo is cached from when
    // the resource was first created in order to create the ResourceURL during the RenderRequest phase. Instead,
    // need to figure out for ourselves whether or not it needs to be updated.
    if (!needsUpdate) {

      // If it's a JavaScript resource that can't be cached (like ICEfaces JavaScript resources that will not
      // initialize properly if cached), then return true for needsUpdate so that the browser will re-retrieve
      // the resource.
      if (NON_CACHED_RESOURCES.contains(resourceName)) {

        needsUpdate = true;
      }

      // Otherwise,
      else {

        if (lastModifiedInSeconds == null) {
          URL url = wrappedResource.getURL();

          if (url != null) {
            InputStream inputStream = null;

            try {
              URLConnection urlConnection = url.openConnection();
              urlConnection.setUseCaches(false);
              urlConnection.connect();
              inputStream = urlConnection.getInputStream();

              long lastModifiedInMilliSeconds = urlConnection.getLastModified();
              lastModifiedInSeconds = new Long((long) (lastModifiedInMilliSeconds / 1000));
            }
            catch (IOException e) {
              lastModifiedInSeconds = new Long(0L);
            }
            finally {

              if (inputStream != null) {

                try {
                  inputStream.close();
                }
                catch (IOException e) {
                  // ignore
                }
              }
            }
          }
          else {
            logger.warn(
              "Unable to determine if user agent needs update because resource URL was null for resourceName=[{0}].",
              resourceName);
          }
        }

        if (lastModifiedInSeconds != null) {
          long ifModifiedHeaderInSeconds = 0L;
          Map<String, String> requestHeaderMap = facesContext.getExternalContext().getRequestHeaderMap();

          if (requestHeaderMap.containsKey(HEADER_IF_MODIFIED_SINCE)) {

            // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25
            String requestHeaderValue = requestHeaderMap.get(HEADER_IF_MODIFIED_SINCE);

            try {

              // Note that SimpleDateFormat is not thread-safe so an instance variable has to be used
              // instead of a static variable.
              // http://www.codefutures.com/weblog/andygrove/2007/10/simpledateformat-and-thread-safety.html
              SimpleDateFormat httpSpecDateFormat = new SimpleDateFormat(HTTP_SPEC_DATE_PATTERN,
                  Locale.US);
              httpSpecDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));

              long ifModifiedHeaderInMilliSeconds = httpSpecDateFormat.parse(requestHeaderValue)
                .getTime();
              ifModifiedHeaderInSeconds = (long) (ifModifiedHeaderInMilliSeconds / 1000L);

              if (logger.isDebugEnabled()) {
                logger.debug(
                  "resourceName=[{0}] requestHeaderValue=[{1}] ifModifiedHeaderInSeconds=[{2}]",
                  resourceName, requestHeaderValue, Long.toString(ifModifiedHeaderInSeconds));
              }
            }
            catch (ParseException e) {
              logger.error("Unable to parse request-header=[{0}] value=[{1}]", HEADER_IF_MODIFIED_SINCE,
                requestHeaderValue);
            }
          }
          else {

            // FACES-1496: Need to get the BridgeContext from the ThreadLocal in order to prevent memory
            // leaks with Mojarra.
            BridgeContext bridgeContext = BridgeContext.getCurrentInstance();
            PortletContainer portletContainer = bridgeContext.getPortletContainer();
            long ifModifiedHeaderInMilliSeconds = portletContainer.getHttpServletRequestDateHeader(
                HEADER_IF_MODIFIED_SINCE);
            ifModifiedHeaderInSeconds = (long) (ifModifiedHeaderInMilliSeconds / 1000);

            if (logger.isDebugEnabled()) {
              logger.debug("resourceName=[{0}] portletContainer ifModifiedHeaderInSeconds=[{1}]",
                resourceName, Long.toString(ifModifiedHeaderInSeconds));
            }
          }

          if (logger.isDebugEnabled()) {
            logger.debug("resourceName=[{0}] lastModified=[{1}] ifModifiedHeaderInSeconds=[{2}]",
              resourceName, Long.toString(lastModifiedInSeconds),
              Long.toString(ifModifiedHeaderInSeconds));
          }

          // FACES-62: Only compare seconds rather than milliseconds since LastModified header typically only
          // contains seconds. This will avoid unnecessary updates.
          needsUpdate = (lastModifiedInSeconds > ifModifiedHeaderInSeconds);
        }
      }
    }

    logger.debug("resourceName=[{0}] needsUpdate=[{1}]", resourceName, needsUpdate);

    return needsUpdate;
  }

  /**
   * Since this method is not supplied by the {@link ResourceWrapper} class it has to be implemented here.
   */
  @Override
  public String getContentType() {
    return wrappedResource.getContentType();
  }

  /**
   * Since this method is not supplied by the {@link ResourceWrapper} class it has to be implemented here.
   */
  @Override
  public void setContentType(String contentType) {
    wrappedResource.setContentType(contentType);
  }

  /**
   * Since this method is not supplied by the {@link ResourceWrapper} class it has to be implemented here.
   */
  @Override
  public String getLibraryName() {
    return wrappedResource.getLibraryName();
  }

  /**
   * Since this method is not supplied by the {@link ResourceWrapper} class it has to be implemented here.
   */
  @Override
  public void setLibraryName(String libraryName) {
    wrappedResource.setLibraryName(libraryName);
  }

  @Override
  public String getRequestPath() {

    // Get the requestPath value from the wrapped resource.
    String wrappedRequestPath = wrappedResource.getRequestPath();
    FacesContext facesContext = FacesContext.getCurrentInstance();

    // For each extension-mapped servlet-mapping found in web.xml, remove the extension from the wrapped requestPath
    // value. This is necessary because both Mojarra and MyFaces assume a servlet environment and automatically
    // append extension-mapped suffixes which have no meaning in a portlet environment.
    if (wrappedRequestPath != null) {

      if (wrappedRequestPath.contains(ResourceHandler.RESOURCE_IDENTIFIER)) {

        BridgeContext bridgeContext = BridgeContext.getCurrentInstance();
        BridgeConfig bridgeConfig = bridgeContext.getBridgeConfig();

        List<ConfiguredServletMapping> configuredFacesServletMappings =
          bridgeConfig.getConfiguredFacesServletMappings();

        if (configuredFacesServletMappings != null) {

          for (ConfiguredServletMapping configuredServletMapping : configuredFacesServletMappings) {

            if (configuredServletMapping.isExtensionMapped()) {
              String extension = configuredServletMapping.getExtension();

              // Note: Both Mojarra and MyFaces construct a requestPath that looks something like
              // "/javax.faces.resource/jsf.js.faces?ln=javax.faces" and so we look for the ".faces?ln" as
              // an indicator that ".faces" needs to be removed from the requestPath.
              String token = extension + StringPool.QUESTION + ResourceConstants.LN;
              int pos = wrappedRequestPath.indexOf(token);

              // If the servlet-mapping extension is found, then remove it since this is an implicit
              // Servlet-API dependency on the FacesServlet that has no meaning in a portlet environment.
              if (pos > 0) {

                wrappedRequestPath = wrappedRequestPath.substring(0, pos) +
                  wrappedRequestPath.substring(pos + extension.length());
                logger.debug("Removed extension=[{0}] from requestPath=[{1}]", extension,
                  wrappedRequestPath);
              }
              else if (wrappedRequestPath.endsWith(extension)) {

                if (extension.equals(EXTENSION_FACES) &&
                    wrappedRequestPath.endsWith(LIBRARY_NAME_JAVAX_FACES)) {
                  // Special case: Don't remove ".faces" if request path ends with "javax.faces"
                  // http://issues.liferay.com/browse/FACES-1202
                }
                else {

                  // Sometimes resources like the ICEfaces bridge.js file don't have a library name
                  // (ln=) parameter and simply look like this:
                  // /my-portlet/javax.faces.resource/bridge.js.faces
                  wrappedRequestPath = wrappedRequestPath.substring(0,
                      wrappedRequestPath.lastIndexOf(extension));
                  logger.debug("Removed extension=[{0}] from requestPath=[{1}]", extension,
                    wrappedRequestPath);
                }
              }
            }
          }
        }
      }

      // If the wrapped request path ends with "org.richfaces" then
      if (wrappedRequestPath.endsWith(ResourceRichFacesImpl.ORG_RICHFACES)) {

        // Check to see if the resource physically exists in the META-INF/resources/org.richfaces folder of the
        // RichFaces JAR. If it does, then this qualifies as a special case in which the
        // ResourceHandlerImpl#fixRichFacesImageURLs(FacesContext, String) method is unable to handle resources
        // such as "node_icon.gif" and the library name must be "org.richfaces.images" instead of
        // "org.richfaces".
        String resourcePath = "META-INF/resources/org.richfaces/" + getResourceName();
        URL resourceURL = getClass().getClassLoader().getResource(resourcePath);

        if (resourceURL != null) {
          wrappedRequestPath = wrappedRequestPath + ".images";
        }
      }
    }

    // In order to have Mojarra's ScriptRenderer and StylesheetRenderer function properly, this method first encodes
    // the URL returned by the wrapped resource.
    String encodedResourceURL = facesContext.getExternalContext().encodeResourceURL(wrappedRequestPath);

    return encodedResourceURL;
  }

  /**
   * Since this method is not supplied by the {@link ResourceWrapper} class it has to be implemented here.
   */
  @Override
  public String getResourceName() {
    return wrappedResource.getResourceName();
  }

  /**
   * Since this method is not supplied by the {@link ResourceWrapper} class it has to be implemented here.
   */
  @Override
  public void setResourceName(String resourceName) {
    wrappedResource.setResourceName(resourceName);
  }

  @Override
  public Resource getWrapped() {
    return wrappedResource;
  }
}
TOP

Related Classes of com.liferay.faces.bridge.application.ResourceImpl

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.