blob: d085e6f323a6597e34a3ff947f9d57de4606ed9c [file] [log] [blame]
/*
* 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.util.concurrent.ConcurrentMap;
import javax.faces.context.FacesContext;
import org.apache.myfaces.trinidad.context.RequestContext;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidad.skin.Skin;
import org.apache.myfaces.trinidad.util.Args;
import org.apache.myfaces.trinidad.util.ToStringHelper;
import org.apache.myfaces.trinidadinternal.style.StyleContext;
import org.apache.myfaces.trinidadinternal.style.StyleProvider;
import org.apache.myfaces.trinidadinternal.style.cache.FileSystemStyleCache;
import org.apache.myfaces.trinidadinternal.style.xml.StyleSheetDocumentUtils;
import org.apache.myfaces.trinidadinternal.style.xml.parse.StyleSheetDocument;
import org.apache.myfaces.trinidadinternal.util.CopyOnWriteArrayMap;
/**
* An extension of the FileSystemStyleCache which defers to
* a Skin for loading the StyleSheetDocument.
*
* @version $Name: $ ($Revision: adfrt/faces/adf-faces-impl/src/main/java/oracle/adfinternal/view/faces/skin/SkinStyleProvider.java#0 $) $Date: 10-nov-2005.18:58:59 $
*/
public class SkinStyleProvider extends FileSystemStyleCache
{
/**
* Returns a shared instance of the SkinStyleProvider.
* The StyleProvider combines styles from two sources. First,
* styles are pulled from the current Skin, which is
* retrieved from the RenderingContext. Then, styles are pulled
* from the custom style sheet, as identified by the (possibly null)
* customStyleSheetPath argument. Styles specified by the custom
* style sheet take precedence over styles provided by the
* Skin.
*
* @param skin The skin for which the style provider is needed.
* @param targetDirectoryPath The full file system path of the
* directory where generated CSS files are stored.
* If the directory does not exist and cannot be
* created, an IllegalArgumentException is thrown.
* @throws IllegalArgumentException This exception is thrown
* if no Skin is found, or if either of the
* paths are invalid.
*/
public static StyleProvider getSkinStyleProvider(
Skin skin,
String targetDirectoryPath
) throws IllegalArgumentException
{
if (skin == null)
throw new IllegalArgumentException(_LOG.getMessage(
"NO_SKIN_SPECIFIED"));
// If the skin is actually one of our request-specific wrapper
// skins, rip off the wrapper and look up the StyleProvider
// for the wrapped skin. If we don't do this, we end up creating
// a new StyleProvider for every request.
if (skin instanceof RequestSkinWrapper)
skin = ((RequestSkinWrapper)skin).getWrappedSkin();
// Create the key object that we use to look up our
// shared SkinStyleProvider instance
ProviderKey key = new ProviderKey(skin,
targetDirectoryPath);
// Get our cache of existing StyleProviders
ConcurrentMap<ProviderKey, StyleProvider> providers = _getProviders();
StyleProvider provider = providers.get(key);
if (provider != null)
{
_LOG.fine("Style provider served from cache {0}", provider);
return provider;
}
// If we haven't created an instance for this skin/custom style sheet
// yet, create it now.
provider = new SkinStyleProvider(skin,
targetDirectoryPath);
// assume caching is "on"
boolean cacheStyleProvider = true;
boolean logStyleProviderCreated = false;
// skin framework caches only internal skins.
// Internal skins are marked by internal SkinProviders.
// So essentially skins created by external SkinProviders or using
// SkinFactory.createSkin are not internal skins.
if (skin instanceof SkinImpl && !((SkinImpl) skin).isCacheable())
{
cacheStyleProvider = false;
logStyleProviderCreated = true;
}
// Store the provider in our cache
if (cacheStyleProvider)
{
StyleProvider existing = providers.putIfAbsent(key, provider);
if (existing != null)
provider = existing;
else
logStyleProviderCreated = true;
}
if (logStyleProviderCreated && _LOG.isFine())
_LOG.fine("Create a new SkinStyleProvider for skin {0} and targetDirectoryPath {1}. Skin cacheability is: {2}",
new Object[]{skin.getId(), targetDirectoryPath, cacheStyleProvider});
return provider;
}
@Override
public String toString()
{
return new ToStringHelper(this)
.append("skinId", _skin.getId())
.toString();
}
/**
* Creates SkinStyleProvider instance.
* Only subclasses should call this method. All other
* clients should use getSkinStyleProvider().
* @param skin The Skin which defines the
* look and feel-specific style information for this
* StyleProvider.
* @param targetDirectoryPath The full file system path
* to the directory where generated CSS files will be
* stored.
* @see #SkinStyleProvider
* @throws IllegalArgumentException This exception is thrown
* if no Skin is null, or if either of the
* paths are invalid.
*/
protected SkinStyleProvider(
Skin skin,
String targetDirectoryPath
) throws IllegalArgumentException
{
super(targetDirectoryPath);
if (skin == null)
throw new IllegalArgumentException(_LOG.getMessage(
"NO_SKIN_SPECIFIED"));
_skin = skin;
}
/**
* Override of FileSystemStyleCache.createStyleSheetDocument().
* Merges the Skin's styles with custom styles to
* produce a single StyleSheetDocument
*/
@Override
protected StyleSheetDocument createStyleSheetDocument(
StyleContext context
)
{
// First, get the StyleSheetDocument for the custom style
// sheet from the FileSystemStyleCache.
StyleSheetDocument customDocument = super.createStyleSheetDocument(
context);
// Now, get the Skin's StyleSheetDocument
StyleSheetDocument skinDocument = null;
// Synchronize access to _skinDocument
synchronized (this)
{
// gets the skin's StyleSheetDocument (it creates it if needed)
skinDocument = _skinDocument =
((DocumentProviderSkin) _skin).getStyleSheetDocument(context);
}
// Merge the two StyleSheetDocuments
return StyleSheetDocumentUtils.mergeStyleSheetDocuments(skinDocument,
customDocument);
}
/**
* Override of FileSystemStyleCache.hasSourceDocumentChanged()
* which checks for changes to the Skin's style sheet.
*/
@Override
protected boolean hasSourceDocumentChanged(StyleContext context)
{
if (super.hasSourceDocumentChanged(context))
return true;
// Just check to see whether the Skin has a new
// StyleSheetDocument.
// Synchronize access to _skinDocument
synchronized (this)
{
return (_skinDocument !=
((DocumentProviderSkin) _skin).getStyleSheetDocument(context));
}
}
/**
* Override of FileSystemStyleCache.getTargetStyleSheetName().
*/
@Override
protected String getTargetStyleSheetName(
StyleContext context,
StyleSheetDocument document
)
{
// Get the base name from the FileSystemStyleCache.
String name = super.getTargetStyleSheetName(context, document);
// Use the LAF's id as a prefix
String id = _skin.getId();
if (id != null)
{
StringBuffer buffer = new StringBuffer(id.length() + name.length() + 1);
// We know that some LAF ids contain the '.' character. Replace
// this with '-' to make the file name look nicer.
buffer.append(id.replace('.', '-'));
buffer.append('-');
buffer.append(name);
return buffer.toString();
}
return name;
}
// Returns a Map which hashes ProviderKeys to shared instances of SkinStyleProviders.
private static ConcurrentMap<ProviderKey, StyleProvider> _getProviders()
{
ConcurrentMap<String, Object> appMap =
RequestContext.getCurrentInstance().getApplicationScopedConcurrentMap();
ConcurrentMap<ProviderKey, StyleProvider> styleProviders =
(ConcurrentMap<ProviderKey, StyleProvider>)appMap.get(_SKIN_PROVIDERS_KEY);
if (styleProviders == null)
{
styleProviders = _createProvidersCache();
ConcurrentMap<ProviderKey, StyleProvider> oldStyleProviders =
(ConcurrentMap<ProviderKey, StyleProvider>)appMap.putIfAbsent(_SKIN_PROVIDERS_KEY, styleProviders);
if (oldStyleProviders != null)
styleProviders = oldStyleProviders;
}
return styleProviders;
}
private static ConcurrentMap<ProviderKey, StyleProvider> _createProvidersCache()
{
return CopyOnWriteArrayMap.newLRUConcurrentMap(_getCacheSize());
}
private static int _getCacheSize()
{
FacesContext context = FacesContext.getCurrentInstance();
String lruCacheSize = context.getExternalContext().getInitParameter(_MAX_SKINS_CACHED);
int lruCacheSizeInt = _MAX_SKINS_CACHED_DEFAULT;
boolean invalidInt = false;
if (lruCacheSize != null && !lruCacheSize.isEmpty())
{
try
{
lruCacheSizeInt = Integer.parseInt(lruCacheSize);
}
catch (NumberFormatException nfe)
{
invalidInt = true;
}
if (lruCacheSizeInt <= 0)
{
invalidInt = true;
}
}
// Invalid number or number less than zero specified by used in context parameter
// so log a warning
if (invalidInt)
{
if (_LOG.isWarning())
_LOG.warning("INVALID_INTEGER_MAX_SKINS_CACHED", new Object[]{lruCacheSize, _MAX_SKINS_CACHED_DEFAULT});
// re-initialize to max size because it could have been assigned to a negative number above while parsing
lruCacheSizeInt = _MAX_SKINS_CACHED_DEFAULT;
}
return lruCacheSizeInt;
}
// Key that we use to retrieve a shared SkinStyleProvider
// instance
private static class ProviderKey
{
public ProviderKey(
Skin skin,
String targetDirectoryPath
)
{
Args.notNull(skin, "skin");
Args.notNull(targetDirectoryPath, "targetDirectoryPath");
_skinId = skin.getId();
_targetDirectoryPath = targetDirectoryPath;
}
@Override
public String toString()
{
return new ToStringHelper(this)
.append("skinId", _skinId)
.append("targetDirectoryPath", _targetDirectoryPath)
.toString();
}
// Test for equality
@Override
public boolean equals(Object o)
{
if (o == this)
return true;
if (!(o instanceof ProviderKey))
return false;
ProviderKey key = (ProviderKey)o;
return (_equals(_skinId, key._skinId) &&
_equals(_targetDirectoryPath, key._targetDirectoryPath));
}
// Produce the hash code
@Override
public int hashCode()
{
int result = 17;
result = 37 * result + _skinId.hashCode();
result = 37 * result + _targetDirectoryPath.hashCode();
return result;
}
// Tests two objects for equality, taking possible nulls
// into account
private boolean _equals(Object o1, Object o2)
{
if (o1 == null)
return (o1 == o2);
return o1.equals(o2);
}
private final String _skinId;
private final String _targetDirectoryPath;
}
// The Skin which provides styles
private Skin _skin;
// The Skin-specific StyleSheetDocument
private StyleSheetDocument _skinDocument;
// Key to cache of shared SkinStyleProvider instances
private static final String _SKIN_PROVIDERS_KEY =
"org.apache.myfaces.trinidadinternal.skin.SKIN_PROVIDERS_KEY";
private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(
SkinStyleProvider.class);
private static final String _MAX_SKINS_CACHED =
"org.apache.myfaces.trinidad.skin.MAX_SKINS_CACHED";
private static final int _MAX_SKINS_CACHED_DEFAULT = 20;
}