org.richfaces.skin.SkinFactoryImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.richfaces.skin.SkinFactoryImpl.java

Source

/**
 * 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.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Map.Entry;

import javax.el.ELContext;
import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
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 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_PATH = "META-INF/skins/%s.skin.properties";

    private static final String USER_SKIN_PATH = "%s.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 = { DEFAULT_SKIN_PATH, USER_SKIN_PATH };
    private static final String[] THEME_PATHS = { "META-INF/themes/%s.theme.properties", "%s.theme.properties" };
    //   private static final String[] DEFAULT_SKIN_PATHS = { DEFAULT_SKIN_PATH };

    //   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<String, Skin> skins = new HashMap<String, Skin>();
    private Map<String, Skin> baseSkins = new HashMap<String, Skin>();
    private Map<String, Properties> sourceProperties = new HashMap<String, Properties>();
    private Map<String, Theme> themes = new HashMap<String, Theme>();

    //   private Properties defaultSkinProperties = null;
    private String skinName = null;
    private ValueExpression skinBinding = null;
    private String baseSkinName = null;
    private ValueExpression 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(FacesContext facesContext, Object currentSkinOrName, boolean isBase) {
        if (null == currentSkinOrName) {
            throw new SkinNotFoundException(Messages.getMessage(Messages.NULL_SKIN_NAME_ERROR));
        }
        Skin currentSkin = null;
        // user binding return skin instance.
        if (currentSkinOrName instanceof Skin) {
            currentSkin = (Skin) currentSkinOrName;
        } else {
            String currentSkinName = currentSkinOrName.toString();

            Map<String, Skin> skinsMap = (isBase ? baseSkins : skins);
            synchronized (skinsMap) {
                currentSkin = (Skin) skinsMap.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(facesContext, currentSkinName, isBase);
                    skinsMap.put(currentSkinName, currentSkin);
                }
            }
        }
        return currentSkin;
    }

    public Skin getDefaultSkin(FacesContext context) {
        return getSkinByName(context, DEFAULT_SKIN_NAME, false);
    }

    public Skin getSkin(FacesContext context) {
        // TODO - cache skin for current thread ? or for current Faces Lifecycle
        // Phase ?
        Object currentSkinOrName = getSkinOrName(context, false);
        return getSkinByName(context, currentSkinOrName, false);
    }

    public Skin getBaseSkin(FacesContext context) {
        Object currentSkinOrName = getSkinOrName(context, true);
        return getSkinByName(context, currentSkinOrName, true);
    }

    //   protected Properties getDefaultSkinProperties() {
    //      if (defaultSkinProperties == null) {
    //         defaultSkinProperties = loadProperties(DEFAULT_SKIN_NAME,DEFAULT_SKIN_PATHS);
    //      }
    //      return defaultSkinProperties;
    //   }

    /**
     * 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
        ValueExpression 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().getExpressionFactory()
                            .createValueExpression(context.getELContext(), currentSkinName, Object.class);
                } else {
                    skin = currentSkinName;
                }

                if (useBase) {
                    baseSkinBinding = binding;
                    baseSkinName = skin;
                } else {
                    skinBinding = binding;
                    skinName = skin;
                }
            }

            // }
        }
        if (binding != null) {
            return binding.getValue(context.getELContext());
        } else {
            return skin;
        }
    }

    private void processProperties(FacesContext context, Map<Object, Object> properties) {
        ELContext elContext = context.getELContext();
        // replace all EL-expressions by prepared ValueBinding ?
        ApplicationFactory factory = (ApplicationFactory) FactoryFinder
                .getFactory(FactoryFinder.APPLICATION_FACTORY);
        Application app = factory.getApplication();

        for (Entry<Object, Object> entry : properties.entrySet()) {
            Object propertyObject = entry.getValue();
            if (propertyObject instanceof String) {
                String property = (String) propertyObject;
                if (ELUtils.isValueReference(property)) {
                    ExpressionFactory expressionFactory = app.getExpressionFactory();
                    entry.setValue(expressionFactory.createValueExpression(elContext, property, Object.class));
                } else {
                    entry.setValue(property);
                }
            }
        }
    }

    /**
     * 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(FacesContext context, String name, boolean isBase) throws SkinNotFoundException {
        Properties skinParams;
        synchronized (sourceProperties) {
            skinParams = sourceProperties.get(name);
            if (skinParams == null) {
                skinParams = loadProperties(name, SKINS_PATHS);
                processProperties(context, skinParams);
                // skinParams = Collections.unmodifiableMap(skinParams);
                sourceProperties.put(name, skinParams);
            }
        }
        BasicSkinImpl skinImpl;
        if (DEFAULT_SKIN_NAME.equals(name)) {
            skinImpl = new DefaultSkinImpl(skinParams);
        } else if (isBase) {
            skinImpl = new BaseSkinImpl(skinParams, this);
        } else {
            skinImpl = new SkinImpl(skinParams, this);
        }

        return skinImpl;
    }

    /**
     * @param name
     * @param defaultProperties
     * @return
     * @throws SkinNotFoundException
     * @throws FacesException
     * @throws ReferenceSyntaxException
     */
    protected Properties loadProperties(String name, String[] paths) throws SkinNotFoundException, FacesException {
        ClassLoader loader = getClassLoader();
        // Get properties for concrete skin.
        Properties skinProperties = new Properties();
        int loadedPropertiesCount = 0;
        for (int i = 0; i < paths.length; i++) {
            String skinPropertiesLocation = paths[i].replaceAll("%s", name);
            if (loadProperties(loader, skinProperties, skinPropertiesLocation)) {
                loadedPropertiesCount++;
            }
        }
        if (loadedPropertiesCount == 0) {
            throw new SkinNotFoundException(Messages.getMessage(Messages.SKIN_NOT_FOUND_ERROR, name));
        }
        return skinProperties;
    }

    /**
     * @return
     */
    protected ClassLoader getClassLoader() {
        return Thread.currentThread().getContextClassLoader();
    }

    /**
     * @param loader
     * @param properties
     * @param location
     */
    protected boolean loadProperties(ClassLoader loader, Properties properties, String location) {
        boolean loaded = false;
        try {
            Enumeration<URL> resources = loader.getResources(location);
            while (resources.hasMoreElements()) {
                URL url = (URL) resources.nextElement();
                InputStream propertyStream = null;
                try {
                    propertyStream = URLToStreamHelper.urlToStream(url);
                    properties.load(propertyStream);
                    loaded = true;
                } 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);
            }
        }
        return loaded;
    }

    @Override
    public Theme getTheme(FacesContext facesContext, String name) {
        Theme theme = themes.get(name);
        if (null == theme) {
            Properties properties;
            try {
                properties = loadProperties(name, THEME_PATHS);
            } catch (SkinNotFoundException e) {
                throw new ThemeNotFoundException(Messages.getMessage(Messages.THEME_NOT_FOUND_ERROR, name),
                        e.getCause());
            }
            processProperties(facesContext, properties);
            theme = new ThemeImpl(properties);
            themes.put(name, theme);
        }
        return theme;
    }
}