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

org.apache.myfaces.trinidadinternal.skin.StyleSheetEntry Maven / Gradle / Ivy

The newest version!
/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you 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.apache.myfaces.trinidadinternal.skin;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;

import java.net.MalformedURLException;
import java.net.URL;

import java.util.List;
import java.util.Map;

import javax.faces.context.FacesContext;

import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidad.share.io.InputStreamProvider;
import org.apache.myfaces.trinidad.share.io.NameResolver;

import org.apache.myfaces.trinidad.util.ClassLoaderUtils;
import org.apache.myfaces.trinidadinternal.share.io.CachingNameResolver;
import org.apache.myfaces.trinidadinternal.share.io.FileInputStreamProvider;
import org.apache.myfaces.trinidadinternal.share.io.URLInputStreamProvider;
import org.apache.myfaces.trinidadinternal.share.xml.JaxpXMLProvider;
import org.apache.myfaces.trinidadinternal.share.xml.ParseContextImpl;
import org.apache.myfaces.trinidadinternal.share.xml.XMLProvider;

import org.apache.myfaces.trinidadinternal.style.StyleContext;
import org.apache.myfaces.trinidadinternal.style.xml.StyleSheetDocumentUtils;
import org.apache.myfaces.trinidadinternal.style.xml.parse.StyleSheetDocument;


/**
 * Package-private utility class used by Skin implementation
 * to manage a single XSS or CSS skin stylesheet source file .
 * This class calls the parsing code which parses either the XSS or CSS file (_createSkinStyleSheet),
 * and it stores a StyleSheetDocument object, which is a parsed representation of a 
 * Trinidad style sheet document whether that is in the css or xss format or merged.
 * This class could actually
 * be pushed into an inner class in Skin, but at the moment
 * it is separated out simply to reduce the amount of code in
 * Skin.java.
 *
 * @version $Name:  $ ($Revision: adfrt/faces/adf-faces-impl/src/main/java/oracle/adfinternal/view/faces/skin/StyleSheetEntry.java#0 $) $Date: 10-nov-2005.18:59:01 $
 */
class StyleSheetEntry
{
  /**
   * Creates a StyleSheetEntry for the specified context and styleSheetName.
   * This method will log any errors/exceptions and return
   * null if the style sheet source file could not be found/parsed.
   */
  public static StyleSheetEntry createEntry(
    StyleContext     context,
    String           styleSheetName
    )
  {
    // In order to create the StyleSheetEntry, we need to locate and
    // parse the style sheet file.  We use a NameResolver to use to
    // find the style sheet.
    NameResolver resolver = _getNameResolver(context, styleSheetName);
    if (resolver == null)
      return null;

    // a private static inner class to store the document, icon, and skin properties
    // =-=jmw @todo Should I just create a StyleSheetEntry directly,
    // and make the constructor public? (probably)
    StyleSheetEntry skinStyleSheet = _createSkinStyleSheet(resolver,
                                                           styleSheetName);


      if (skinStyleSheet == null)
        return null;

      // We either a special subclass of StyleSheetEntry that will recalculate 
      // the StyleSheetEntry if the skin is dirty or if the web.xml's 
      // CHECK_FILE_MODIFICATION flag is set and there are file modifications.
      boolean checkStylesModified = context.checkStylesModified();
      return new CheckModifiedEntry(styleSheetName,
                                    skinStyleSheet.getDocument(),
                                    resolver,
                                    checkStylesModified);





  }

  // Creates a StyleSheetEntry which never checks for
  // modifications.
  // =-=jmw there is a hotspot bug I filed with JDeveloper
  // 4102252 when this is private I get an IllegalAccessException
  // in CheckModifiedEntry. changing it to package private
  StyleSheetEntry(
    String                 styleSheetName,
    StyleSheetDocument     document
    )
  {
    _name       = styleSheetName;
    _document   = document;

  }

  StyleSheetEntry(String styleSheetName)
  {
    this(styleSheetName, null);
  }

  // Use full constructor
  private StyleSheetEntry()
  {
  }

  /**
   * Returns the name of the style sheet source file
   * for this StyleSheetEntry.
   */
  public String getStyleSheetName()
  {
    return _name;
  }

  /**
   * Returns the StyleSheetDocument for this
   * StyleSheetEntry.
   */
  public StyleSheetDocument getDocument()
  {
    return _document;
  }

  /**
   * Checks whether the underlying style sheet source file
   * has been modified and if so, reloads the StyleSheetDocument.
   * Returns true if the document has been modified (and the
   * StyleSheetDocument has been reloaded).
   */
  public boolean checkModified(StyleContext context)
  {
    return false;
  }
  
  // Gets a File for the specified name, or returns null if no file exists
  // Try the local styles directory.
  public static File resolveLocalFile(File localStylesDir, String name)
  {
    // Try the local styles directory
    File file = new File(localStylesDir, name);
    if (file.exists())
      return file;

    return null;
  }

  // Gets an URL for the specified name using ClassLoaderUtils.getResource
  public static URL resolveClassLoaderURL(String name)
  {
    if (name == null)
      return null;
    return ClassLoaderUtils.getResource(name);

  }

  // Gets an URL for the non static urls -- that is, urls that could change after the
  // server has started.
  public static URL resolveNonStaticURL(String name)
  {
    if (name == null)
      return null;
    FacesContext fContext = FacesContext.getCurrentInstance();
    if (fContext != null)
    {
      try
      {
        if (name.startsWith("http:") ||
            name.startsWith("https:") ||
            name.startsWith("file:") ||
            name.startsWith("ftp:") ||
            name.startsWith("jar:"))
        {
          URL url = new URL(name);
          if (url != null)
            return url;
        }
        else
        {
          String rootName = _getRootName(name);
          // Return a URL for the application resource mapped to the specified path,
          // if it exists; otherwise, return null.
          URL url = fContext.getExternalContext().getResource(rootName);
          if (url != null)
            return url;
        }
      }
      catch (MalformedURLException e)
      {
        // Eat the MalformedURLException - maybe the name isn't an URL
        ;
      }
    }
    return null;
  }  
  

  // Called by CheckModifiedEntry when the style sheet has changed
  void __setDocument(StyleSheetDocument document)
  {
    _document = document;
  }

  // Creates the SkinStyleSheet (a private static inner class that
  // contains StyleSheetDocument plus a list
  // of properties) from a CSS file
  //
  private static StyleSheetEntry _createSkinStyleSheet(
    NameResolver     resolver,
    String           styleSheetName
    )
  {

    StyleSheetEntry skinStyleSheet = null;

    if (styleSheetName.endsWith(".css"))
    {
      // this will parse a skin css file which allows icons, properties,
      // and styles.
        skinStyleSheet =  _createSkinStyleSheetFromCSS(resolver,
                                                       styleSheetName);
    } else {
      String message = _LOG.getMessage("INVALID_STYLESHEET_TYPE", new Object[]{styleSheetName});
      _LOG.severe(message);
    }

    return skinStyleSheet;

  }


  // Creates the StyleSheetEntry from a skinning file that ends in .css
  private static StyleSheetEntry _createSkinStyleSheetFromCSS(
    NameResolver     resolver,
    String           styleSheetName
    )
  {

     try
     {
        // We simply use a ParseContext as a place to store parameters like
       // inputStreamProviders and nameResolvers that will be reused when parsing
        ParseContextImpl parseContext = new ParseContextImpl();
        // if this is a utility that isn't in this file, then I can't return a SkinStyleSheet.
        // I think instead this parseCSSSource should return a new instance of StyleSheetEntry.
        return SkinStyleSheetParserUtils.parseCSSSource(
                                    parseContext,
                                    resolver,
                                    styleSheetName,
                                    StyleSheetEntry.class);
     }
     catch (Exception e)
     {
       if (_LOG.isSevere())
         _LOG.severe("CANNOT_LOAD_STYLESHEET", styleSheetName);
         _LOG.severe(e);

     }
      return null;
  }

  // Returns the NameResolver to use for locating and loading style sheet file.
  // Depending upon what the styleSheetName is, we load the file different way: local file,
  // url, etc.
  private static NameResolver _getNameResolver(
    StyleContext context,
    String       styleSheetName
    )
  {
    // get localStylesDirectory
    File localStylesDir = _getStylesDir(context);

    // Make sure we have some styles directory
    if ((localStylesDir == null))
    {
      _LOG.warning(_STYLES_DIR_ERROR);
      return null;
    }
    NameResolver resolver = null;

    try
    {
      resolver =
          _getNameResolverForStyleSheetFile(context, localStylesDir, styleSheetName);
    }
    catch (IOException e)
    {
      if (_LOG.isWarning())
        _LOG.warning("CANNOT_LOAD_STYLESHEET", styleSheetName);
        _LOG.warning(e.getMessage());
    }
    if (resolver == null)
    {
      // If we can't get a NameResolver, something is seriously wrong.
      // createResolver() logged the error already, so just return null.
      return null;
    }

    // Wrap up the resolver in a CachingNameResolver that we can
    // can use to check for updates to imported style sheets
    return new CachingNameResolver(resolver, null, true);
  }
  
  /**
   * 

* This method tries to find the Skin's stylesheet file (e.g., purple-desktop.css). * It creates a NameResolver object, and it returns the NameResolver object. * A NameResolver object contains an * InputStreamProvider (this object loads the file) and a sub- NameResolver * that finds files that are relative to the base file, like an @import file in a .css file. *

*

* This method tries to find the stylesheet file, first locally, or using an url, or a static url, * then we create a StyleSheetNameResolver and we pass in the InputStreamProvider we created that * we know can find the file. If we can't find the file any of these ways, then we check * META-INF/services for a NameResolver service. This is how a third party can customize * how they can find files, by supplying a META-INF/services NameResolver implementation. *

* @param context * @param localStylesDir File the local styles directory * @param filename the stylesheet name * @return NameResolver - either a StyleSheetNameResolver or the META-INF/services NameResolver * implementation. The META-INF/services NameResolver implementation is the way a third party * can customize the way they find and load files. * @throws FileNotFoundException when the file could not be found in all of the ways we tried to find it. */ private static NameResolver _getNameResolverForStyleSheetFile( StyleContext context, File localStylesDir, String filename) throws IOException { InputStreamProvider provider = null; File file = StyleSheetEntry.resolveLocalFile(localStylesDir, filename); if (file != null) provider = new FileInputStreamProvider(file); if (provider == null) { // Gets an URL for the specified name. // Try a few different means to get the file as an url and then create the appropriate // InputStreamProvider from that URL. URL url = resolveNonStaticURL(filename); if (url != null) provider = new URLInputStreamProvider(url); else { // see if it is an URL that can be loaded by the ClassLoader. // We create a StaticURLInputStreamProvider from the url because we consider the // url static because it can't be changed without restarting the server, so we don't // need to check if the source has changed. url = resolveClassLoaderURL(filename); if (url != null) provider = new StaticURLInputStreamProvider(url); } } // If at this point we have found an InputStreamProvider, then we will create a // StyleSheetNameResolver. Otherwise, we need to check for a custom NameResolver. if (provider != null) return StyleSheetNameResolver.createResolver(context, localStylesDir, provider); // If we still can't locate the file at this point, then look for a custom // NameResolver specified as a META-INF\services. NameResolver servicesNameResolver = _loadNameResolverFromServices(filename); if (servicesNameResolver != null) { if (_LOG.isFine()) { _LOG.fine("Using the InputStreamProvider from META-INF\\services"); } return servicesNameResolver; } // If we couldn't locate the file, throw an IOException throw new FileNotFoundException(_getFileNotFoundMessage(localStylesDir, filename)); } // Subclass of StyleSheetEntry which recreates the StyleSheetEntry // if the skin is marked dirty (skin.isDirty()) or if the underlying // source files have been modified and CHECK_FILE_MODIFICATION flag is set // in web.xml. private static class CheckModifiedEntry extends StyleSheetEntry { public CheckModifiedEntry( String styleSheetName, StyleSheetDocument document, NameResolver resolver, boolean checkFileModifiedFlagSet ) { super(styleSheetName, document); // We need the InputStreamProvider in order to check // for modifications. Get it from the NameResolver. _provider = _getInputStreamProvider(resolver); _checkFileModifiedFlagSet = checkFileModifiedFlagSet; } // Override of checkModified() which first checks if the file // needs to be reparsed and a new CSS file generated. // The conditions are if the skin is marked dirty, or if the // web.xml's CHECK_FILE_MODIFICATION flag is set and the source // has changed. The InputStreamProvider's hasSourceChanged method // is called to see if the source has changed. @Override public boolean checkModified(StyleContext context) { // We would synchronize here, but at the moment synchronization // is provided by Skin.getStyleSheetDocument(). if (context.isDirty() || (_checkFileModifiedFlagSet && ((_provider != null) && (_provider.hasSourceChanged()))) ) { // Throw away the old InputStreamProvider and StyleSheetDocument _provider = null; __setDocument(null); // Get a new NameResolver String name = getStyleSheetName(); NameResolver resolver = _getNameResolver(context, name); if (resolver != null) { // Recreate the StyleSheetEntry for the styleSheet using the new NameResolver // (e.g., if purpleSkin.css // has changed, create the SkinStyleSheetEntry for purpleSkin.css) // Using a new NameResolver like we do ensures that we don't get a // cached result from the provider // (see SkinStyleSheetParserUtils.parseCSSSource's getCachedResult) StyleSheetEntry skinStyleSheet = _createSkinStyleSheet(resolver, name); if (skinStyleSheet != null) { _provider = _getInputStreamProvider(resolver); __setDocument(skinStyleSheet.getDocument()); } return true; } } return false; } private InputStreamProvider _getInputStreamProvider( NameResolver resolver ) { // Note: We assume that we are using a CachingNameResolver, // and that the InputStreamProvider for the source file has // already been retrieved. That way, when we call // NameResolver.getProvider(), we are actually getting // the same InputStreamProvider that was used to read the // style sheet earlier. assert (resolver instanceof CachingNameResolver); try { return resolver.getProvider(getStyleSheetName()); } catch (IOException e) { // We shouldn't get here - we know we were able to // get the InputStreamProvider before - so we should be // able to get the cached InputStreamProvider now assert false; } return null; } private InputStreamProvider _provider; private boolean _checkFileModifiedFlagSet; } // Construct error message for the specified file name private static String _getFileNotFoundMessage(File localStylesDir, String name) { StringBuffer buffer = new StringBuffer(); buffer.append("Unable to locate the skin's style sheet \""); buffer.append(name); buffer.append("\" in "); if (localStylesDir != null) { buffer.append("local styles directory ("); buffer.append(localStylesDir.getPath()); buffer.append("), "); } buffer.append("or on the class path.\n"); buffer.append("Please be sure that this style sheet is installed."); return buffer.toString(); } // Returns the File corresponding to the styles directory - either // the local directory or the shared directory - depending on the // shared value private static File _getStylesDir( StyleContext context) { String contextPath = context.getGeneratedFilesPath(); // We only need to look up the shared styles path if the shared // context path is non-null. If the shared context path is null, // we don't have a shared styles directory (and calling // Configuration.getPath() may throw a DirectoryUnavailableException). if (contextPath == null) return null; String stylesPath = contextPath + "/adf/styles"; // Convert the path to a File File stylesDir = new File(stylesPath); // Make sure the directory actually exists if (stylesDir.exists()) return stylesDir; return null; } // Returns a name which can be resolved relative to the // ServletContext root. private static String _getRootName(String name) { // Tack on a starting "/" if the name doesn't already have one - // seems to be required by ServletContext.getRealPath() and // ServletContext.getResource() - at least on OC4J. return (name.startsWith("/")) ? name : ("/" + name); } /** * Returns an instance of NameResolver that was set in META-INF\services. * This is used only if the stylesheet cannot be found any other way. * This way third party users can create their own way to find the file e.g., MDS. * * @return a NameResolver instance that has been defined in META-INF\services\ * org.apache.myfaces.trinidad.share.io.NameResolver // In this file they will have a line like "org.mycompany.io.MyNameResolverImpl". * null if no NameResolver is found. */ static private NameResolver _loadNameResolverFromServices(String name) { // We don't want to check services every time, so instead store it on the applicationMap. FacesContext context = FacesContext.getCurrentInstance(); Map appMap = context.getExternalContext().getApplicationMap(); // Is it stored on the application map already? If so, use it. NameResolver savedResolver = (NameResolver)appMap.get(_SERVICES_RESOLVER_KEY); if (savedResolver != null) return savedResolver; List resolvers = ClassLoaderUtils.getServices( _NAME_RESOLVER_CLASS_NAME); for (NameResolver customNameResolver : resolvers) { InputStreamProvider provider = null; try { provider = customNameResolver.getProvider(name); } catch (IOException e) { // Log fine message. Try the next factory to get a provider if (_LOG.isFine()) _LOG.fine(_SERVICES_RESOLVER_IOEXCEPTION_MSG); } // Found a customNameResolver with a provider. So store it away and return it from the method. if (provider != null) { appMap.put(_SERVICES_RESOLVER_KEY, customNameResolver); return customNameResolver; } } return null; } // A subclass of URLInputStreamProvider which never checks for // modifications private static class StaticURLInputStreamProvider extends URLInputStreamProvider { public StaticURLInputStreamProvider(URL url) { super(url); } @Override public boolean hasSourceChanged() { return false; } } // for META-INF\services\org.apache.myfaces.trinidad.share.io.NameResolver // In this file they will have a line like "org.mycompany.io.MyNameResolverImpl" static private final String _NAME_RESOLVER_CLASS_NAME = NameResolver.class.getName(); // Error messages private static final String _STYLES_DIR_ERROR = "Could not locate the Trinidad styles directory." + "Please be sure that the Trinidad installable resources are installed."; private static final String _SERVICES_RESOLVER_IOEXCEPTION_MSG = "IOException when calling the META-INF/services NameResolver's getProvider method. " + "Trying next nameResolver."; private static final String _SERVICES_RESOLVER_KEY = "org.apache.myfaces.trinidadinternal.skin.SERVICES_RESOLVER_KEY"; private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(StyleSheetEntry.class); private String _name; private StyleSheetDocument _document; }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy