/**
* License Agreement.
*
* Rich Faces - Natural Ajax for Java Server Faces (JSF)
*
* Copyright (C) 2007 Exadel, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License version 2.1 as published by the Free Software Foundation.
*
* 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.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.richfaces.skin;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.faces.FacesException;
import javax.faces.FactoryFinder;
import javax.faces.application.Application;
import javax.faces.application.ApplicationFactory;
import javax.faces.context.FacesContext;
import javax.faces.el.ReferenceSyntaxException;
import javax.faces.el.ValueBinding;
import org.ajax4jsf.Messages;
import org.ajax4jsf.resource.util.URLToStreamHelper;
import org.ajax4jsf.util.ELUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Implementation of {@link SkinFactory} with building skins from properties
* files.
*
* @author shura
*
*/
public class SkinFactoryImpl extends SkinFactory {
/**
* Name of web application init parameter for current default
* {@link javax.faces.render.RenderKit } interaction. by default -
* org.exadel.chameleon.RENDERKIT_DEFINITION . TODO Possible Values
*/
public static final String RENDER_KIT_PARAMETER = "org.ajax4jsf.RENDERKIT_DEFINITION";
/**
* Resource Uri for properties file with default values of skin parameters.
*/
private static final String DEFAULT_SKIN_PROPERTIES_RESOURCE = "META-INF/skins/DEFAULT.skin.properties";
private static final String DEFAULT_CONFIGURATION_RESOURCE = "META-INF/skins/DEFAULT.configuration.properties";
/**
* Name of default skin . "DEFAULT" in this realisation.
*/
private static final String DEFAULT_SKIN_NAME = "DEFAULT";
/**
* Path in jar to pre-defined vendor and custom user-defined skins
* definitions. in this realisation "META-INF/skins/" for vendor , "" -
* user-defined.
*/
private static final String[] SKINS_PATHS = {
"META-INF/skins/%s.skin.properties", "%s.skin.properties" };
private static final String[] DEFAULT_SKIN_PATHS = { DEFAULT_SKIN_PROPERTIES_RESOURCE };
private static final String[] CONFIGURATIONS_PATHS = {
"META-INF/skins/%s.configuration.properties",
"%s.configuration.properties" };
private static final String[] DEFAULT_CONFIGURATION_PATHS = { "META-INF/skins/DEFAULT.configuration.properties" };
private Map skins = Collections.synchronizedMap(new HashMap());
private Properties defaultSkinProperties = null;
private String skinName = null;
private ValueBinding skinBinding = null;
private String baseSkinName = null;
private ValueBinding baseSkinBinding = null;
private static final Log log = LogFactory.getLog(SkinFactoryImpl.class);
private static final String A4J_BASE_SKIN_PARAMETER = "org.ajax4jsf.BASE_SKIN";
private static final String A4J_SKIN_PARAMETER = "org.ajax4jsf.SKIN";
protected Skin getSkinByName(Object currentSkinOrName) {
if (null == currentSkinOrName) {
throw new SkinNotFoundException(Messages
.getMessage(Messages.NULL_SKIN_NAME_ERROR));
}
// user binding return skin instance.
if (currentSkinOrName instanceof Skin) {
return (Skin) currentSkinOrName;
}
String currentSkinName = currentSkinOrName.toString();
Skin currentSkin = (Skin) skins.get(currentSkinName);
// LAZY creation for skins, since, in case of EL expressions
// for skin name, we don't can know all names of existing skins.
if (currentSkin == null) {
if (log.isDebugEnabled()) {
log.debug(Messages.getMessage(Messages.CREATE_SKIN_INFO,
currentSkinName));
}
currentSkin = buildSkin(currentSkinName, getDefaultSkinProperties());
skins.put(currentSkinName, currentSkin);
}
return currentSkin;
}
public Skin getDefaultSkin(FacesContext context) {
return getSkinByName(DEFAULT_SKIN_NAME);
}
public Skin getSkin(FacesContext context) {
// TODO - cache skin for current thread ? or for current Faces Lifecycle
// Phase ?
Object currentSkinOrName = getSkinOrName(context, false);
return getSkinByName(currentSkinOrName);
}
public Skin getBaseSkin(FacesContext context) {
Object currentSkinOrName = getSkinOrName(context, true);
return getSkinByName(currentSkinOrName);
}
protected Properties getDefaultSkinProperties() {
if (defaultSkinProperties == null) {
defaultSkinProperties = loadDefaultProperties(DEFAULT_SKIN_PROPERTIES_RESOURCE);
}
return defaultSkinProperties;
}
/**
*
*/
private Properties loadDefaultProperties(String path) {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Properties defaultProperties = new Properties();
try {
Enumeration properties = loader.getResources(path);
while (properties.hasMoreElements()) {
URL url = (URL) properties.nextElement();
InputStream propertyStream = null;
try {
propertyStream = URLToStreamHelper.urlToStream(url);
defaultProperties.load(propertyStream);
} catch (IOException e) {
// DO Nothing...
log.info(Messages.getMessage(
Messages.DEFAULT_PROPERTIES_NOT_FOUND_ERROR, path),
e);
continue;
} finally {
if (null != propertyStream) {
propertyStream.close();
}
}
}
} catch (IOException e) {
log.info(Messages.getMessage(
Messages.DEFAULT_PROPERTIES_NOT_FOUND_ERROR, path), e);
}
return defaultProperties;
}
/**
* Calculate name for current skin. For EL init parameter store value
* binding for speed calculations.
*
* @param context
* @param useBase
* @return name of currens skin from init parameter ( "DEFAULT" if no
* parameter ) or {@link Skin } as result of evaluation EL
* expression.
*/
protected Object getSkinOrName(FacesContext context, boolean useBase) {
// Detect skin name
ValueBinding binding;
String skin;
synchronized (this) {
if (useBase) {
binding = baseSkinBinding;
skin = baseSkinName;
} else {
binding = skinBinding;
skin = skinName;
}
if (binding == null && skin == null) {
String currentSkinName = context.getExternalContext()
.getInitParameter(
useBase ? BASE_SKIN_PARAMETER : SKIN_PARAMETER);
if (null == currentSkinName) {
// Check for a old ( deprecated ) parameter name.
currentSkinName = context.getExternalContext()
.getInitParameter(
useBase ? A4J_BASE_SKIN_PARAMETER
: A4J_SKIN_PARAMETER);
if (null != currentSkinName) {
log.warn("Init parameter for a skin name changed to "+ (useBase ? BASE_SKIN_PARAMETER
: SKIN_PARAMETER));
}
}
if (currentSkinName == null) {
// not set - usr default.
return DEFAULT_SKIN_NAME;
}
if (ELUtils.isValueReference(currentSkinName)) {
// For EL expression as skin name
binding = context.getApplication().createValueBinding(
currentSkinName);
} else {
skin = currentSkinName;
}
}
if (useBase) {
baseSkinBinding = binding;
baseSkinName = skin;
} else {
skinBinding = binding;
skinName = skin;
}
// }
}
if (binding != null) {
return binding.getValue(context);
} else {
return skin;
}
}
/**
* Factory method for build skin from properties files. for given skin name,
* search in classpath all resources with name 'name'.skin.properties and
* append in content to default properties. First, get it from
* META-INF/skins/ , next - from root package. for any place search order
* determined by {@link java.lang.ClassLoader } realisation.
*
* @param name
* name for builded skin.
* @param defaultProperties
* @return skin instance for current name
* @throws SkinNotFoundException -
* if no skin properies found for name.
*/
protected Skin buildSkin(String name, Properties defaultProperties)
throws SkinNotFoundException {
Map skinParams = loadProperties(name, defaultProperties, SKINS_PATHS);
return new SkinImpl(skinParams);
}
/**
* @param name
* @param defaultProperties
* @return
* @throws SkinNotFoundException
* @throws FacesException
* @throws ReferenceSyntaxException
*/
protected Map loadProperties(String name, Properties defaultProperties,
String[] paths) throws SkinNotFoundException, FacesException,
ReferenceSyntaxException {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
// Get properties for concrete skin.
Properties skinProperties = new Properties(defaultProperties);
int loadedPropertiesCount = 0;
for (int i = 0; i < paths.length; i++) {
try {
String skinPropertiesLocation = paths[i].replaceAll("%s", name);
Enumeration properties = loader
.getResources(skinPropertiesLocation);
while (properties.hasMoreElements()) {
URL url = (URL) properties.nextElement();
InputStream propertyStream = null;
try {
propertyStream = URLToStreamHelper.urlToStream(url);
skinProperties.load(propertyStream);
loadedPropertiesCount++;
} catch (IOException e) {
log.warn(Messages
.getMessage(Messages.SKIN_PROPERTIES_IO_ERROR),
e);
continue;
} finally {
if (null != propertyStream) {
propertyStream.close();
}
}
}
} catch (IOException e) {
// Do nothing - we can only log error, and continue to load next
// property.
if (log.isInfoEnabled()) {
log.info(Messages
.getMessage(Messages.SKIN_PROPERTIES_IO_ERROR), e);
}
}
}
// TODO - read skin from property file in WEB-INF directory.
if (loadedPropertiesCount == 0) {
throw new SkinNotFoundException(Messages.getMessage(
Messages.SKIN_NOT_FOUND_ERROR, name));
}
// replace all EL-expressions by prepared ValueBinding ?
ApplicationFactory factory = (ApplicationFactory) FactoryFinder
.getFactory(FactoryFinder.APPLICATION_FACTORY);
Application app = factory.getApplication();
Map skinParams = new HashMap();
for (Enumeration e = skinProperties.propertyNames(); e
.hasMoreElements();) {
String propertyName = (String) e.nextElement();
String property = skinProperties.getProperty(propertyName);
if (null != property) {
int count = 0;
while (property.startsWith("&")) {
property = skinProperties
.getProperty(property.substring(1));
if (null == property) {
throw new FacesException(Messages.getMessage(
Messages.SKIN_ILLEGAL_REFERENCE, propertyName));
}
if (count++ > 1000) {
throw new FacesException(Messages.getMessage(
Messages.SKIN_CYCLIC_REFERENCE, propertyName));
}
}
if (ELUtils.isValueReference(property)) {
skinParams.put(propertyName, app
.createValueBinding(property));
} else {
skinParams.put(propertyName, property);
}
}
}
return skinParams;
}
}