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

org.apache.myfaces.trinidadinternal.skin.provider.SkinProviderRegistry Maven / Gradle / Ivy

There is a newer version: 2.2.1
Show 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.provider;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;

import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidad.skin.Skin;
import org.apache.myfaces.trinidad.skin.SkinMetadata;
import org.apache.myfaces.trinidad.skin.SkinProvider;
import org.apache.myfaces.trinidad.skin.SkinVersion;
import org.apache.myfaces.trinidad.util.ClassLoaderUtils;
import org.apache.myfaces.trinidadinternal.skin.SkinUtils;

/**
 * Internal implementation of SkinProvider which is exposed using SkinProvider.getCurrentInstance()
 * This class collates all SkinProvider SPIs, Trinidad SkinProviders and SkinFactory to return the
 * best match for a Skin requested This class reads all registered SPIs using
 * org.apache.myfaces.trinidad.skin.SkinProvider
 * 

* Instance of this class is created and put into the ExternalContext during bootstrap in * GlobalConfiguratorImpl and retrieved in the static factory inside SkinProvider *

*/ public class SkinProviderRegistry extends SkinProvider { public SkinProviderRegistry() { ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext(); List services = ClassLoaderUtils.getServices(SkinProvider.class.getName()); if (_LOG.isFine()) _LOG.fine("Adding providers from registered SPIs... " + services.size()); List providers = new ArrayList(services.size() + 2); providers.addAll(services); // add internal skin providers at the end // this is to give the other SPIs a chance before the internal skin providers if (_LOG.isFine()) _LOG.fine("Adding TrinidadSkinProvider... "); providers.add(TrinidadSkinProvider.getCurrentInstance(externalContext)); if (_LOG.isFine()) _LOG.fine("Adding TrinidadBaseSkinProvider... "); // this provider serves static base skins, so we can create a new instance here providers.add(new TrinidadBaseSkinProvider()); if (_LOG.isFine()) _LOG.fine("Adding ExternalSkinProvider... "); providers.add(ExternalSkinProvider.getCurrentInstance(externalContext)); _providers = Collections.unmodifiableList(providers); } @Override public Collection getSkinMetadata(ExternalContext externalContext) { Collection metadata = new ArrayList(); for (SkinProvider provider : _providers) metadata.addAll(provider.getSkinMetadata(externalContext)); return Collections.unmodifiableCollection(metadata); } /** * @param externalContext * @param skinMetadata search criteria object containing the information of skin to be queried id, * family, renderKit, version are the information used from skin metadata to * perform search for the skin requested. Other fields do not participate in * the search. * @return matching skin for the search criteria passed, if none found, return the simple skin for * the renderKit in search criteria if renderKit not specified in search criteria, skinMetadata * automatically assumes it as SkinMetadata.RenderKitId.DESKTOP */ @Override public Skin getSkin(ExternalContext externalContext, SkinMetadata skinMetadata) { _handleCircularDependency(skinMetadata); List matchingSkins = new ArrayList(); Skin matchingSkin = null; if (_LOG.isFine()) _LOG.fine("Skin request for metadata: " + skinMetadata); for (SkinProvider provider : _providers) { matchingSkin = provider.getSkin(externalContext, skinMetadata); if (matchingSkin != null) { // log which provider got skin etc if (_LOG.isFine()) _LOG.fine("Skin obtained for metadata: " + skinMetadata + " from provider: " + provider); matchingSkins.add(matchingSkin); } } if (_LOG.isFine()) _LOG.fine("Matches obtained for " + skinMetadata + ": " + matchingSkins.size()); // now we are done with asking all skin providers // ensure that the matching skins returned are indeed matching the search criteria // onus is on SkinProviderRegistry to serve the skin requested, even if a SkinProvider // returns a wrong skin. // filter the results obtained from skin providers based on id, family and renderKit // filtering on version is more complicated and taken up subsequently matchingSkins = _filterSkins(matchingSkins, skinMetadata); if (matchingSkins.isEmpty()) { if (_LOG.isFine()) _LOG.fine("NO MATCH. Will return a simple skin or null for skin metadata: " + skinMetadata); assert (skinMetadata.getRenderKitId() != null); // if renderKit is available return the simple skin for that renderKit // skinMetadata.getRenderKitId() can never be null, default renderKit will always be DESKTOP matchingSkin = SkinUtils.getDefaultSkinForRenderKitId(this, externalContext, skinMetadata.getRenderKitId()); _processSkinForReturn(skinMetadata, matchingSkin); return matchingSkin; } // we have at least one match now // if we have only one match // return that skin if (matchingSkins.size() == 1) { if (_LOG.isFine()) _LOG.fine("returning ONLY match for skin metadata: " + skinMetadata); matchingSkin = matchingSkins.get(0); _processSkinForReturn(skinMetadata, matchingSkin); return matchingSkin; } // at this point we have more than one matches for the search criteria passed // so, extract the best match based on version return _versionFilter(skinMetadata, matchingSkins); } /** * exposing providers for testability * @return */ List getProviders() { return _providers; } /** * ensure sanity in the list of matches received by filtering the skins based on id, family and * renderKit * * @param skins * @param metadata * @return */ private List _filterSkins(List skins, SkinMetadata metadata) { if (skins == null || skins.isEmpty() || metadata == null) return Collections.emptyList(); List filterList = _idFilter(skins, metadata.getId()); // if the id based filtering resulted in only match then return that // provided id was mentioned in search criteria if (filterList.size() == 1 && metadata.getId() != null) { return filterList; } // if more than one match for id or if id is not specified, then filter by family filterList = _familyFilter(filterList, metadata.getFamily()); // if the family based filtering resulted in only match then return that // provided family was mentioned in search criteria if (filterList.size() == 1 && metadata.getFamily() != null) { return filterList; } // if family based filtering resulted in multiple matches, or if family and id are not // specified, // proceed with renderKit based filtering. filterList = _renderKitFilter(filterList, metadata.getRenderKitId()); return filterList; } /** * filter based on skin id, if mentioned. Otherwise return the list as is * * @param skins * @param id * @return */ private List _idFilter(List skins, String id) { if (id == null || id.isEmpty()) return skins; List filterList = new ArrayList(skins.size()); for (Skin skin : skins) if (id.equals(skin.getId())) filterList.add(skin); return filterList; } /** * filter based on skin family, if mentioned. Otherwise return the list as is * * @param skins * @param family * @return */ private List _familyFilter(List skins, String family) { if (family == null || family.isEmpty()) return skins; List filterList = new ArrayList(skins.size()); for (Skin skin : skins) if (family.equalsIgnoreCase(skin.getFamily())) filterList.add(skin); return filterList; } /** * filter based on skin renderkit id, if mentioned. Otherwise return the list as is * * @param skins * @param renderKitId * @return */ private List _renderKitFilter(List skins, String renderKitId) { if (renderKitId == null || renderKitId.isEmpty()) return skins; List filterList = new ArrayList(skins.size()); for (Skin skin : skins) if (renderKitId.equalsIgnoreCase(skin.getRenderKitId())) filterList.add(skin); return filterList; } /** * filter based on version if version is mentioned try to return exact match else if version is * not mentioned or version is default try to return default skin else try to return leaf skin * else return first match * * @param searchCriteria * @param matchingSkins * @return */ private Skin _versionFilter(SkinMetadata searchCriteria, List matchingSkins) { SkinVersion version = searchCriteria.getVersion(); Skin matchingSkin; // we can now find the best match based on version // now that there is a version mentioned and skins matching is > 1 // look for exact name match matchingSkin = _findSkinForVersionName(matchingSkins, version); // if we found exact match in version, return it. if (matchingSkin != null) { if (_LOG.isFine()) _LOG.fine("returning exact version match."); _processSkinForReturn(searchCriteria, matchingSkin); return matchingSkin; } // either user is looking for default version or did not find the version requested // try to find a default skin matchingSkin = _findSkinWithDefaultVersion(matchingSkins); // if we found the default version skin, return it. if (matchingSkin != null) { if (_LOG.isFine()) _LOG.fine("returning DEFAULT version match."); _processSkinForReturn(searchCriteria, matchingSkin); return matchingSkin; } // the version that user asked for is not available // so find leaf skin matchingSkin = _findLeafSkin(matchingSkins); // if we found a leaf skin return that if (matchingSkin != null) { if (_LOG.isFine()) _LOG.fine("return LEAF skin or one of the matches."); _processSkinForReturn(searchCriteria, matchingSkin); return matchingSkin; } // we failed to find any better result for the given version, so return first match if (_LOG.isFine()) _LOG.fine("nothing worked so return first match."); matchingSkin = matchingSkins.get(0); _processSkinForReturn(searchCriteria, matchingSkins.get(0)); return matchingSkin; } /** * This method processes a skin that is being returned from the provider. * 1. remove the skin from the context tracking * 2. add any missing skin additions to the skin * @param skinMetadata * @return */ private void _processSkinForReturn(SkinMetadata skinMetadata, Skin skin) { FacesContext context = FacesContext.getCurrentInstance(); _clearReturningSkinContext(context, skinMetadata); // before returning the skin, ensure that all skin additions defined in trinidad-skins.xml // are added. TrinidadSkinProvider.getCurrentInstance(context.getExternalContext()).ensureSkinAdditions(skin); } /** * We track the skin being requested to build the hierarchy so that we can identify circular * dependencies. This method clears the information for the present Skin that is being returned. * @param context * @param skinMetadata */ private void _clearReturningSkinContext(FacesContext context, SkinMetadata skinMetadata) { Map attrMap = context.getAttributes(); Object o = attrMap.get(_SKIN_PROVIDER_CONTEXT); List requesters; if (o != null) { requesters = (List) o; // remove the skinMetadata requesters.remove(skinMetadata); if (_LOG.isFiner()) { _LOG.finer("Removing " + skinMetadata + " from context"); _LOG.finer("Context now is " + requesters); } } } /** * find a skin with version passed * * @param skins * @param version * @return */ private Skin _findSkinForVersionName(Collection skins, SkinVersion version) { if (version == null) throw new IllegalArgumentException("skin version cannot be null"); if (version.getName() == null || version.getName().isEmpty()) return null; for (Skin skin : skins) { // metadata cannot be null and also version inside it cannot be null if (version.getName().equals(skin.getVersion().getName())) { if (_LOG.isFine()) _LOG.fine("Found version match skin: " + skin); return skin; } } return null; } /** * Latest skin is the one which is last in the family hierarchy eg: fusion-v1 -> fusion-v2 -> * fusion-v3 Among this fusion-v3 is the latest. So we look for a skin that is not extended by any * other skin in the family. * * @param skins * @return */ private Skin _findLeafSkin(Collection skins) { List leafSkins = new ArrayList(); List parentIds = new ArrayList(); // collect parents skins among the list for (Skin metadata : skins) { Skin baseSkin = metadata.getBaseSkin(); if (baseSkin != null) { String parentId = baseSkin.getId(); if (parentId != null) parentIds.add(parentId); } } // find leaf skins, which is not in parent list for (Skin skin : skins) { String skinId = skin.getId(); if (skinId != null && !parentIds.contains(skinId)) { leafSkins.add(skin); } } // if there are no leaves, return null // this is a rare case, almost impossible since there will be // at least one skin which is not parent of another // but let us cover the corner case if any if (leafSkins.isEmpty()) return null; // if there is one leaf skin return that // if there are many, return the last one among the leaves return leafSkins.get(leafSkins.size() - 1); } /** * find a skin that has its SkinVersion set to 'default', if it exists. * * @param matchingSkinList A list of Skins that we will look through to find the 'default'. * @return Skin with SkinVersion isDefault true, otherwise, null. */ private Skin _findSkinWithDefaultVersion(Collection matchingSkinList) { for (Skin skin : matchingSkinList) { SkinVersion skinVersion = skin.getVersion(); if (skinVersion != null && skinVersion.isDefault()) { if (_LOG.isFine()) _LOG.fine("Found default skin: " + skin); return skin; } } return null; } /** * We track the skin being requested to build the hierarchy so that we can identify circular * dependencies. This method adds the currently requested skin into the context. If a skin is * re-requested, then an exception will be thrown * @param skinMetadata * @throws IllegalStateException if a circular dependency is detected */ private void _handleCircularDependency(SkinMetadata skinMetadata) { FacesContext context = FacesContext.getCurrentInstance(); Map attrMap = context.getAttributes(); Object o = attrMap.get(_SKIN_PROVIDER_CONTEXT); List requesters = null; if (o == null) { requesters = new ArrayList(); attrMap.put(_SKIN_PROVIDER_CONTEXT, requesters); } else requesters = (List) o; if (requesters.contains(skinMetadata)) { String message = "Circular dependency detected while loading skin: " + skinMetadata + ". Requesters are: " + requesters; if (_LOG.isSevere()) _LOG.severe(message); throw new IllegalStateException(message); } // add the currently requested metadata into list requesters.add(skinMetadata); if (_LOG.isFiner()) { _LOG.finer("Adding " + skinMetadata + " to context"); _LOG.finer("Context now is " + requesters); } } private final List _providers; private static final String _SKIN_PROVIDER_CONTEXT = "org.apache.myfaces.trinidadinternal.skin.provider.SkinProviderRegistry.Context"; private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(SkinProviderRegistry.class); }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy