All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.faces.application.resource.ResourceImpl Maven / Gradle / Ivy

There is a newer version: 4.1.1
Show newest version
/*
 * Copyright (c) 1997, 2021 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package com.sun.faces.application.resource;

import static com.sun.faces.util.Util.getFacesMapping;
import static com.sun.faces.util.Util.getFirstWildCardMappingToFacesServlet;
import static com.sun.faces.util.Util.getLastModified;
import static com.sun.faces.util.Util.isResourceExactMappedToFacesServlet;
import static jakarta.faces.application.ProjectStage.Development;
import static jakarta.faces.application.ProjectStage.Production;
import static jakarta.faces.application.ResourceHandler.FACES_SCRIPT_LIBRARY_NAME;
import static jakarta.faces.application.ResourceHandler.FACES_SCRIPT_RESOURCE_NAME;
import static jakarta.faces.application.ResourceHandler.RESOURCE_IDENTIFIER;
import static jakarta.servlet.http.MappingMatch.EXACT;
import static jakarta.servlet.http.MappingMatch.PATH;
import static java.util.Collections.emptyMap;
import static java.util.Locale.US;
import static java.util.logging.Level.FINEST;

import java.io.Externalizable;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.net.URL;
import java.net.URLConnection;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.sun.faces.application.ApplicationAssociate;
import com.sun.faces.util.FacesLogger;

import jakarta.faces.application.ProjectStage;
import jakarta.faces.application.Resource;
import jakarta.faces.application.ResourceHandler;
import jakarta.faces.context.ExternalContext;
import jakarta.faces.context.FacesContext;
import jakarta.servlet.http.HttpServletMapping;
import jakarta.servlet.http.HttpServletRequest;

/**
 * Default implementation of {@link jakarta.faces.application.Resource}. The ResourceImpl instance itself has the same
 * lifespan as the request, however, the ResourceInfo instances that back this object are cached by the ResourceManager
 * to reduce the time spent scanning for resources.
 */
public class ResourceImpl extends Resource implements Externalizable {

    // Log instance for this class
    private static final Logger LOGGER = FacesLogger.APPLICATION.getLogger();

    /* HTTP Date format required by the HTTP/1.1 RFC */
    private static final String RFC1123_DATE_PATTERN = "EEE, dd MMM yyyy HH:mm:ss zzz";

    private static final String IF_MODIFIED_SINCE = "If-Modified-Since";

    private static final TimeZone GMT = TimeZone.getTimeZone("GMT");

    /* The meta data on the resource */
    private transient ResourceInfo resourceInfo;

    /*
     * Response headers that need to be added by the ResourceManager implementation.
     */
    private transient Map responseHeaders;

    /**
     * Time when this application was started. This is used to generate expiration headers.
     */
    private long initialTime;

    /**
     * Lifespan of this resource for caching purposes.
     */
    private long maxAge;

    /**
     * The URL of this {@link ResourceImpl} object is valid exactly as long as this {@link ResourceImpl} object itself is valid.
     * Therefore, resolve the URL only once and re-use the already resolved URL value for subsequent calls to {@link ResourceImpl#getURL()}.
     */
    private URL resolvedUrl = null;

    // ------------------------------------------------------------ Constructors

    /**
     * Necessary for serialization.
     */
    @SuppressWarnings({ "UnusedDeclaration" })
    public ResourceImpl() {
    }

    /**
     * Creates a new instance of ResourceBase
     * @param resourceInfo the resource info
     * @param contentType the resource content type
     * @param initialTime the resource initial time
     * @param maxAge the resource max age
     */
    public ResourceImpl(ResourceInfo resourceInfo, String contentType, long initialTime, long maxAge) {

        this.resourceInfo = resourceInfo;
        super.setResourceName(resourceInfo.getName());
        super.setLibraryName(resourceInfo.getLibraryInfo() != null ? resourceInfo.getLibraryInfo().getName() : null);
        super.setContentType(contentType);
        this.initialTime = initialTime;
        this.maxAge = maxAge;
    }

    @Override
    public boolean equals(Object o) {

        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        ResourceImpl resource = (ResourceImpl) o;

        return resourceInfo.equals(resource.resourceInfo);
    }

    @Override
    public int hashCode() {
        return resourceInfo.hashCode();
    }

    // --------------------------------------------------- Methods from Resource

    /**
     * @see jakarta.faces.application.Resource#getInputStream()
     */
    @Override
    public InputStream getInputStream() throws IOException {
        initResourceInfo();
        return resourceInfo.getHelper().getInputStream(resourceInfo, FacesContext.getCurrentInstance());
    }

    /**
     * @see jakarta.faces.application.Resource#getURL()
     */
    @Override
    public URL getURL() {
        if (resolvedUrl != null) {
            // fast path - re-use the already resolved url
            return resolvedUrl;
        }

        URL url = resourceInfo.getHelper().getURL(resourceInfo, FacesContext.getCurrentInstance());

        // remember this url for subsequent calls to this method
        resolvedUrl = url;

        return url;
    }

    /**
     * 

* Implementation note. Any values added to getResponseHeaders() will only be visible across multiple calls to this * method when servicing a resource request (i.e. * {@link ResourceHandler#isResourceRequest(jakarta.faces.context.FacesContext)} returns true). If we're * not servicing a resource request, an empty Map will be returned and the values added are effectively thrown away. *

* * @see jakarta.faces.application.Resource#getResponseHeaders() */ @Override public Map getResponseHeaders() { if (isResourceRequest()) { if (responseHeaders == null) { responseHeaders = new HashMap<>(6, 1.0f); } if (FacesContext.getCurrentInstance().isProjectStage(Development)) { responseHeaders.put("Cache-Control", "no-store, must-revalidate"); } else { responseHeaders.put("Cache-Control", "max-age=" + (maxAge/1000)); } URL url = getURL(); InputStream in = null; try { URLConnection conn = url.openConnection(); conn.setUseCaches(false); conn.connect(); in = conn.getInputStream(); long lastModified = getLastModified(url); long contentLength = conn.getContentLength(); if (lastModified == 0) { lastModified = initialTime; } SimpleDateFormat format = new SimpleDateFormat(RFC1123_DATE_PATTERN, US); format.setTimeZone(GMT); responseHeaders.put("Last-Modified", format.format(new Date(lastModified))); if (lastModified != 0 && contentLength != -1) { responseHeaders.put("ETag", "W/\"" + contentLength + '-' + lastModified + '"'); } } catch (IOException ioe) { if (LOGGER.isLoggable(FINEST)) { LOGGER.log(FINEST, "Closing stream", ioe); } } finally { if (in != null) { try { in.close(); } catch (IOException ioe) { if (LOGGER.isLoggable(FINEST)) { LOGGER.log(FINEST, "Closing stream", ioe); } } } } return responseHeaders; } else { return emptyMap(); } } /** * @see jakarta.faces.application.Resource#getRequestPath() */ @Override public String getRequestPath() { FacesContext context = FacesContext.getCurrentInstance(); HttpServletMapping mapping = getFacesMapping(context); String uri = null; // Check for exact mapping first if (mapping.getMappingMatch() == EXACT) { String resource = RESOURCE_IDENTIFIER + '/' + getResourceName(); // Check if the FacesServlet is exact mapped to the resource if (isResourceExactMappedToFacesServlet(context.getExternalContext(), resource)) { uri = resource; } else { // No exact mapping for the requested resource, see if Facelets servlet is mapped to // e.g. /faces/* or *.xhtml and take that mapping mapping = getFirstWildCardMappingToFacesServlet(context.getExternalContext()); if (mapping == null) { // If there are only exact mappings and the resource is not exact mapped, // we can't serve this resource throw new IllegalStateException("No suitable mapping for FacesServlet found. To serve resources " + "FacesServlet should have at least one prefix or suffix mapping."); } } } if (uri == null) { if (mapping.getMappingMatch() == PATH) { // If it is prefix/path mapped, e.g /faces/* -> /faces/jakarta.faces.resource/name uri = mapping.getPattern().replace("/*", RESOURCE_IDENTIFIER) + '/' + getResourceName(); } else { // If it is prefix/path mapped, e.g *.xhtml -> /jakarta.faces.resource/name.xhtml uri = RESOURCE_IDENTIFIER + '/' + mapping.getPattern().replace("*", getResourceName()); } } boolean queryStarted = false; if (getLibraryName() != null) { queryStarted = true; uri += "?ln=" + getLibraryName(); } String version = ""; initResourceInfo(); if (resourceInfo.getLibraryInfo() != null && resourceInfo.getLibraryInfo().getVersion() != null) { version += resourceInfo.getLibraryInfo().getVersion().toString(); } if (resourceInfo.getVersion() != null) { version += resourceInfo.getVersion().toString(); } if (version.length() > 0) { uri += (queryStarted ? "&v=" : "?v=") + version; queryStarted = true; } String localePrefix = resourceInfo.getLocalePrefix(); if (localePrefix != null) { uri += (queryStarted ? "&loc=" : "?loc=") + localePrefix; queryStarted = true; } String contract = resourceInfo.getContract(); if (contract != null) { uri += (queryStarted ? "&con=" : "?con=") + contract; queryStarted = true; } if (FACES_SCRIPT_RESOURCE_NAME.equals(getResourceName()) && FACES_SCRIPT_LIBRARY_NAME.equals(getLibraryName())) { ProjectStage stage = context.getApplication().getProjectStage(); switch (stage) { case Development: uri += queryStarted ? "&stage=Development" : "?stage=Development"; break; case SystemTest: uri += queryStarted ? "&stage=SystemTest" : "?stage=SystemTest"; break; case UnitTest: uri += queryStarted ? "&stage=UnitTest" : "?stage=UnitTest"; break; default: assert stage.equals(Production); } } return context.getApplication().getViewHandler().getResourceURL(context, uri); } /** * @see jakarta.faces.application.Resource#userAgentNeedsUpdate(jakarta.faces.context.FacesContext) */ @Override public boolean userAgentNeedsUpdate(FacesContext context) { // PENDING(edburns): this is a sub-optimal implementation choice // done in the interest of prototyping. It's never a good idea // to do a switch statement based on the type of an object. if (resourceInfo instanceof FaceletResourceInfo) { return true; } // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html // 14.25 If-Modified-Since // if the requested variant has not been modified since the time // specified in this field, an entity will not be returned from the // server; instead, a 304 (not modified) response will be returned // without any message-body. // A date which is later than the server's current time is // invalid. Map requestHeaders = context.getExternalContext().getRequestHeaderMap(); if (requestHeaders.containsKey(IF_MODIFIED_SINCE)) { initResourceInfo(); /* * Make sure that we strip the milliseconds out of what comes back from the getLastModified call for a resource as the * 'If-Modified-Since' header does not use milliseconds. */ long lastModifiedOfResource = ((ClientResourceInfo) resourceInfo).getLastModified(context) / 1000 * 1000; long lastModifiedHeader = getIfModifiedHeader(context.getExternalContext()); if (0 == lastModifiedOfResource) { long startupTime = ApplicationAssociate.getInstance(context.getExternalContext()).getTimeOfInstantiation(); return startupTime > lastModifiedHeader; } else { return lastModifiedOfResource > lastModifiedHeader; } } return true; } // --------------------------------------------------------- Private Methods /* * This method should only be called if the 'If-Modified-Since' header is present in the request header map. */ private long getIfModifiedHeader(ExternalContext extcontext) { Object request = extcontext.getRequest(); if (request instanceof HttpServletRequest) { // try to use the container where we can. V3 for instance // has a FastHttpDateFormat format/parse implementation // which is more than likely more performant than SimpleDateFormat // (otherwise, why would it be there?). return ((HttpServletRequest) request).getDateHeader(IF_MODIFIED_SINCE); } else { SimpleDateFormat format = new SimpleDateFormat(RFC1123_DATE_PATTERN, Locale.US); try { Date ifModifiedSinceDate = format.parse(extcontext.getRequestHeaderMap().get(IF_MODIFIED_SINCE)); return ifModifiedSinceDate.getTime(); } catch (ParseException ex) { if (LOGGER.isLoggable(Level.WARNING)) { LOGGER.log(Level.WARNING, "faces.application.resource.invalid_if_modified_since_header", new Object[] { extcontext.getRequestHeaderMap().get(IF_MODIFIED_SINCE) }); if (ex != null) { LOGGER.log(Level.WARNING, "", ex); } } return -1; } } } // --------------------------------------------- Methods from Externalizable @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(getResourceName()); out.writeObject(getLibraryName()); out.writeObject(getContentType()); out.writeLong(initialTime); out.writeLong(maxAge); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { setResourceName((String) in.readObject()); setLibraryName((String) in.readObject()); setContentType((String) in.readObject()); initialTime = in.readLong(); maxAge = in.readLong(); } private void initResourceInfo() { if (resourceInfo != null) { return; } ResourceManager manager = ApplicationAssociate.getInstance(FacesContext.getCurrentInstance().getExternalContext()).getResourceManager(); resourceInfo = manager.findResource(getLibraryName(), getResourceName(), getContentType(), FacesContext.getCurrentInstance()); } // --------------------------------------------------------- Private Methods private boolean isResourceRequest() { FacesContext ctx = FacesContext.getCurrentInstance(); return ctx.getApplication().getResourceHandler().isResourceRequest(ctx); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy