/*
* � Copyright IBM Corp. 2013
*
* Licensed 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.
*/
/*
* Author: Maire Kehoe (mkehoe@ie.ibm.com)
* Date: 6 Jun 2008
* MergeWarningsTest.java
*/
package com.ibm.xsp.test.framework.registry.annotate;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.w3c.dom.Element;
import com.ibm.commons.util.StringUtil;
import com.ibm.xsp.library.FacesClassLoader;
import com.ibm.xsp.library.LibraryServiceLoader;
import com.ibm.xsp.library.LibraryWrapper;
import com.ibm.xsp.registry.AbstractFacesDefinition;
import com.ibm.xsp.registry.FacesConverterDefinition;
import com.ibm.xsp.registry.FacesDefinition;
import com.ibm.xsp.registry.FacesExtensibleNode;
import com.ibm.xsp.registry.FacesLibraryFragment;
import com.ibm.xsp.registry.FacesProject;
import com.ibm.xsp.registry.FacesRegistry;
import com.ibm.xsp.registry.FacesRenderKitFragment;
import com.ibm.xsp.registry.FacesRendererDefinition;
import com.ibm.xsp.registry.FacesValidatorDefinition;
import com.ibm.xsp.registry.SharableRegistryImpl;
import com.ibm.xsp.registry.config.ConfigRegisterer;
import com.ibm.xsp.registry.config.FacesClassLoaderFactory;
import com.ibm.xsp.registry.parse.ConfigParserFactory;
import com.ibm.xsp.registry.parse.ElementUtil;
import com.ibm.xsp.registry.parse.RegistryAnnotater;
import com.ibm.xsp.registry.parse.RegistryAnnotaterInfo;
import com.ibm.xsp.test.framework.AbstractXspTest;
import com.ibm.xsp.test.framework.ConfigUtil;
import com.ibm.xsp.test.framework.TestProject;
import com.ibm.xsp.test.framework.XspTestUtil;
import com.ibm.xsp.test.framework.setup.SkipFileContent;
/**
* @author Maire Kehoe (mkehoe@ie.ibm.com) 6 Jun 2008 Unit:
* MergeWarningsTest.java
*/
public class MergeWarningsTest extends AbstractXspTest {
private static final String JSF_CORE_NAMESPACE = "http://www.ibm.com/xsp/jsf/core";
public static final String JSF_HTML_NAMESPACE = "http://www.ibm.com/xsp/jsf/html";
private SharableRegistryImpl _jsfRegistry;
private FacesClassLoader _facesLoader;
@Override
public String getDescription() {
// note, to actually see the merge warnings, run MergeWarningsPrinter.
return "that validator and converter ids are only declared once "
+ "in the faces-configs, to prevent merge warnings in the logs";
}
@Override
protected void setUp() throws Exception {
super.setUp();
// note, don't need to discard the already-parsed registries
// because this is parsing faces-config.xml files,
// not xsp-config files.
RegistryAnnotater annotator = new ConverterForClassAnnotater();
ConfigParserFactory.addAnnotater(annotator);
try{
String id ="library_"+(int)(Math.random()*10000);
_jsfRegistry = new SharableRegistryImpl(id);
populateFacesConfigRegistry();
}finally{
ConfigParserFactory.removeAnnotater(annotator);
}
}
public void testPresentInJsfConfig() throws Exception {
FacesRegistry reg = _jsfRegistry;
Map<String, FacesDefinition> keyToDependsDef = new HashMap<String, FacesDefinition>();
List<String> libraryFilePaths;
{
String targetLibId = ConfigUtil.getTargetLibrary(this);
LibraryWrapper targetLib = LibraryServiceLoader.getLibrary(targetLibId);
String[] targetLibPaths = targetLib.getFacesConfigFiles();
libraryFilePaths = new ArrayList<String>(Arrays.asList(targetLibPaths));
Collections.sort(libraryFilePaths);
}
List<FacesDefinition> libraryDefs = new ArrayList<FacesDefinition>();
// populate the Maps above with depends ids, and build a list of library defs
for (FacesDefinition def : getDefs(reg)) {
if( Collections.binarySearch(libraryFilePaths, def.getFile().getFilePath()) >= 0){
// library def
libraryDefs.add(def);
continue;
}// else depends def
String key = computeKey(def);
keyToDependsDef.put(key, def);
}
String fails = "";
Map<String, FacesDefinition> keyToLibDef = new HashMap<String, FacesDefinition>();
for (FacesDefinition def : libraryDefs) {
String key = computeKey(def);
// store the key/def & check for previously stored
FacesDefinition existingDef = keyToLibDef.put(key, def);
if( null == existingDef ){
existingDef = keyToDependsDef.get(key);
}
if( null == existingDef ){
// not overriding existing def
continue;
}
// fail, overriding existing def.
String fail = path(def)+ " Merge " + key
+ " (overrides: "+ path(existingDef) + ")";
fails += fail + "\n";
}
fails = XspTestUtil.removeMultilineFailSkips(fails,
SkipFileContent.concatSkips(getSkips(), this, "testPresentInJsfConfig"));
if (fails.length() > 0) {
fail(XspTestUtil.getMultilineFailMessage(fails));
}
}
private String computeKey(FacesDefinition def) {
if (def instanceof FacesValidatorDefinition) {
FacesValidatorDefinition val = (FacesValidatorDefinition) def;
String validatorId = val.getValidatorId();
return "[validator-id:"+validatorId+"]";
}
if( def instanceof FacesRendererDefinition ){
FacesRendererDefinition ren = (FacesRendererDefinition) def;
String key = ren.getRenderKitFragment().getRenderKitId()+ " "+ren.getId();
return "[renderer:"+key+"]";
}
if( def instanceof ConverterForClassDefinition ){
ConverterForClassDefinition forClass = (ConverterForClassDefinition)def;
return "[converter-for-class:"+forClass.getReferenceId()+"]";
}
if( def instanceof FacesConverterDefinition ){
FacesConverterDefinition con = (FacesConverterDefinition) def;
String converterId = con.getConverterId();
return "[converter-id:" +converterId+ "]";
}
throw new RuntimeException("Unknown def type: "+def);
}
private String path(FacesDefinition def) {
return def.getFile().getFilePath();
}
protected String[] getSkips(){
return StringUtil.EMPTY_STRING_ARRAY;
}
@SuppressWarnings("unchecked")
private List<FacesDefinition> getDefs(FacesRegistry reg){
List<FacesDefinition> defs = findNonRenDefsByProject(reg);
for (Iterator<FacesDefinition> i = defs.iterator(); i.hasNext();) {
FacesDefinition def = i.next();
if (def instanceof FacesValidatorDefinition) {
//keep
}
else if (def instanceof FacesConverterDefinition) {
// keep
}
else{
i.remove();
}
}
List<FacesRendererDefinition> rens = findRendererDefsByProject(reg);
defs.addAll(rens);
for (FacesProject proj : reg.getProjectList()) {
for (FacesLibraryFragment file : proj.getFiles()) {
List<ConverterForClassDefinition> forClassList = (List<ConverterForClassDefinition>)
file.getExtension("converter-for-class-list");
if( null != forClassList ){
defs.addAll(forClassList);
}
}
}
// Collections.reverse(defs);
return defs;
}
private List<FacesRendererDefinition> findRendererDefsByProject(
FacesRegistry reg) {
List<FacesRendererDefinition> defs = new ArrayList<FacesRendererDefinition>();
for (FacesProject proj : reg.getProjectList()) {
for (FacesLibraryFragment file : proj.getFiles()) {
for (String kitId : file.getRenderKitIds()) {
FacesRenderKitFragment kitFrag = file.getRenderKitFragment(kitId);
defs.addAll(kitFrag.getDefs());
}
}
}
return defs;
}
private List<FacesDefinition> findNonRenDefsByProject(FacesRegistry reg) {
List<FacesDefinition> defs = new ArrayList<FacesDefinition>();
for (FacesProject proj : reg.getProjectList()) {
for (FacesLibraryFragment file : proj.getFiles()) {
defs.addAll(file.getDefs());
}
}
return defs;
}
private void populateFacesConfigRegistry() {
for (String path : TestProject.findFacesConfigPaths(this)) {
if( "META-INF/core-faces-config.xml".equals(path) ){
// register the xsp-config jsf-base.xsp-config
registerJsfBase();
registerJsfImpl();
// fall through to register core-faces-config.xml
}
URL url = getFacesConfigUrlEndingWith(path, path);
if( null == url ){
throw new RuntimeException("Cannot find " +path+
" on classpath.");
}
String placeholderNamespace = "placeholderNamespaceUri";
registerJsfFacesConfig(url, placeholderNamespace, path);
}
_jsfRegistry.refreshReferences();
}
private void registerJsfBase() {
String container = "com.ibm.xsp.core/bin/";
String namespace = JSF_CORE_NAMESPACE;
String relativePath = "META-INF/jsf-base.xsp-config";
URL coreCommon = getFacesConfigUrlEndingWith(container + relativePath,
relativePath);
if(coreCommon==null) {
fail(relativePath);
}
registerJsfFacesConfig(coreCommon, namespace, relativePath);
}
private void registerJsfImpl() {
String location = "jsf-impl.jar!/";
//check for JSF 1.0
String relativePath = "com/sun/faces/jsf-ri-config.xml";
String end = location + relativePath;
URL validUrl = getFacesConfigUrlEndingWith(end, relativePath);
if( null != validUrl ){
//JSF 1.0 is available
// (note the jsf-ri-runtime.xml is available in both 1.0 and 1.1
// but the 1.0-only files have more detail)
// add the jsf core namespace validators and converters
// in the jsf core namespace
registerJsfFacesConfig(validUrl,
JSF_CORE_NAMESPACE, relativePath);
// add the renderers to the jsf html namespace
relativePath = "com/sun/faces/standard-html-renderkit.xml";
end = location + relativePath;
validUrl = getFacesConfigUrlEndingWith(end, relativePath);
assertNotNull(relativePath, validUrl);
registerJsfFacesConfig(validUrl,
JSF_HTML_NAMESPACE, relativePath);
}else{
// assume JSF 1.1
//JSF 1.1
relativePath = "com/sun/faces/jsf-ri-runtime.xml";
end = location + relativePath;
validUrl = getFacesConfigUrlEndingWith(end, relativePath);
assertNotNull(relativePath, validUrl);
// Note, this file contains both core and html, but is
// registered only under html
// because that's were the renderers should be found.
//register them all in the jsf html namespace
registerJsfFacesConfig(validUrl,
JSF_HTML_NAMESPACE, relativePath);
}
}
/**
* @param refs
* @param facesConfigUrl
* @param namespace
* @param relativePath
* the path relative to the project. The facesConfigUrl is
* expected to end with that path.
*/
private FacesLibraryFragment registerJsfFacesConfig(URL facesConfigUrl,
String namespace, String relativePath) {
// calculate the jar root by subtracting the relative path from the
// configUrl
String urlStr = facesConfigUrl.toString();
assertTrue(urlStr.endsWith(relativePath));
int rootIndex = urlStr.length() - relativePath.length();
String root = urlStr.substring(0, rootIndex);
// Note, this is being used to load the META-INF/faces-config.xml files
FacesProject project = getProject(root);
if (null == project) { // the project doesn't already exist
project = _jsfRegistry.createProject(root);
}
if (null == _facesLoader) {
_facesLoader = FacesClassLoaderFactory.createContext(this
.getClass());
}
FacesLibraryFragment file = ConfigRegisterer.getInstance().registerProjectConfig(project, _facesLoader, null, null,
relativePath, facesConfigUrl, null, namespace);
assertNotNull(file);
return file;
}
/**
* Gets the project in the specified registry whose PageFileSystem has the
* specified id.
*/
private FacesProject getProject(String id){
for (FacesProject proj : _jsfRegistry.getProjectList()) {
if( id.equals( proj.getId() )){
return proj;
}
}
return null;
}
/**
* @param partial
* a string found in the url of the file being searched for
* @param resourceRelative
* the resource path and name relative to the root.
*/
private URL getFacesConfigUrlEndingWith(String end,
String resourceRelative) {
try {
Enumeration<?> facesConfigs = FacesClassLoaderFactory
.findContextClassLoader(this.getClass()).getResources(
resourceRelative);
while (facesConfigs.hasMoreElements()) {
URL url = (URL) facesConfigs.nextElement();
String urlPath = url.toString();
if (urlPath.endsWith(end)) {
return url;
}
}
} catch (IOException ex) {
ex.printStackTrace();
fail(ex.getMessage());
}
// this time don't check the container folder
URL url = FacesClassLoaderFactory.findContextClassLoader(getClass()).getResource(resourceRelative);
if( null != url ){
return url;
}
//fail("No faces-config.xml file found whose path ends with the string \""
// + end + "\"");
return null;
}
protected static class ConverterForClassDefinition extends AbstractFacesDefinition{
public Class<?> forClass;
public ConverterForClassDefinition(FacesLibraryFragment file,
Class<?> forClass, Class<?> javaClass) {
super(file, /*id*/forClass.getName(), javaClass, null, /*referenceId*/forClass.getName());
this.forClass = forClass;
}
public FacesDefinition getParent() {
return null;
}
public Class<?> getForClass() {
return forClass;
}
public boolean isTag() {
return false;
}
}
private class ConverterForClassAnnotater implements RegistryAnnotater{
public void annotate(RegistryAnnotaterInfo info,
FacesExtensibleNode parsed, Element elem) {
if( parsed instanceof FacesLibraryFragment ){
List<ConverterForClassDefinition> forClassDefs = createForClassDefs((FacesLibraryFragment)parsed, elem);
if( ! forClassDefs.isEmpty() ){
parsed.setExtension("converter-for-class-list", forClassDefs);
}
}
}
private List<ConverterForClassDefinition> createForClassDefs(
FacesLibraryFragment file, Element elem) {
List<ConverterForClassDefinition> defs = new ArrayList<ConverterForClassDefinition>();
for (Element converterElem : ElementUtil.getChildren(elem, "converter")) {
String forNameStr = ElementUtil.extractValue(converterElem, "converter-for-class");
if( null == forNameStr ){
continue;
}
Class<?> forNameClass = loadClass(forNameStr);
String converterClassStr = ElementUtil.extractValue(converterElem, "converter-class");
Class<?> converterClass = loadClass(converterClassStr);
defs.add(new ConverterForClassDefinition(file, forNameClass, converterClass));
}
return defs;
}
private Class<?> loadClass(String className){
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
}