001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018 package org.apache.geronimo.myfaces.deployment; 019 020 import java.io.IOException; 021 import java.net.MalformedURLException; 022 import java.net.URL; 023 import java.util.ArrayList; 024 import java.util.Collection; 025 import java.util.HashMap; 026 import java.util.List; 027 import java.util.Map; 028 import java.util.StringTokenizer; 029 import java.util.jar.JarFile; 030 031 import javax.faces.webapp.FacesServlet; 032 033 import org.apache.commons.logging.Log; 034 import org.apache.commons.logging.LogFactory; 035 import org.apache.geronimo.common.DeploymentException; 036 import org.apache.geronimo.deployment.ModuleIDBuilder; 037 import org.apache.geronimo.deployment.service.EnvironmentBuilder; 038 import org.apache.geronimo.deployment.util.DeploymentUtil; 039 import org.apache.geronimo.deployment.xmlbeans.XmlBeansUtil; 040 import org.apache.geronimo.gbean.AbstractName; 041 import org.apache.geronimo.gbean.AbstractNameQuery; 042 import org.apache.geronimo.gbean.GBeanData; 043 import org.apache.geronimo.gbean.GBeanInfo; 044 import org.apache.geronimo.gbean.GBeanInfoBuilder; 045 import org.apache.geronimo.j2ee.annotation.Holder; 046 import org.apache.geronimo.j2ee.deployment.EARContext; 047 import org.apache.geronimo.j2ee.deployment.Module; 048 import org.apache.geronimo.j2ee.deployment.ModuleBuilderExtension; 049 import org.apache.geronimo.j2ee.deployment.NamingBuilder; 050 import org.apache.geronimo.j2ee.deployment.WebModule; 051 import org.apache.geronimo.j2ee.j2eeobjectnames.NameFactory; 052 import org.apache.geronimo.kernel.GBeanAlreadyExistsException; 053 import org.apache.geronimo.kernel.Naming; 054 import org.apache.geronimo.kernel.config.Configuration; 055 import org.apache.geronimo.kernel.config.ConfigurationStore; 056 import org.apache.geronimo.kernel.repository.Environment; 057 import org.apache.geronimo.myfaces.LifecycleProviderGBean; 058 import org.apache.geronimo.schema.SchemaConversionUtils; 059 import org.apache.geronimo.xbeans.javaee.FacesConfigDocument; 060 import org.apache.geronimo.xbeans.javaee.FacesConfigManagedBeanType; 061 import org.apache.geronimo.xbeans.javaee.FacesConfigType; 062 import org.apache.geronimo.xbeans.javaee.FullyQualifiedClassType; 063 import org.apache.geronimo.xbeans.javaee.ParamValueType; 064 import org.apache.geronimo.xbeans.javaee.ServletType; 065 import org.apache.geronimo.xbeans.javaee.WebAppType; 066 import org.apache.geronimo.xbeans.javaee.ListenerType; 067 import org.apache.myfaces.webapp.StartupServletContextListener; 068 import org.apache.xbean.finder.ClassFinder; 069 import org.apache.xmlbeans.XmlCursor; 070 import org.apache.xmlbeans.XmlException; 071 import org.apache.xmlbeans.XmlObject; 072 073 /** 074 * @version $Rev $Date 075 */ 076 public class MyFacesModuleBuilderExtension implements ModuleBuilderExtension { 077 078 private static final Log log = LogFactory.getLog(MyFacesModuleBuilderExtension.class); 079 080 private final Environment defaultEnvironment; 081 private final AbstractNameQuery providerFactoryNameQuery; 082 private final NamingBuilder namingBuilders; 083 private static final String CONTEXT_LISTENER_NAME = StartupServletContextListener.class.getName(); 084 private static final String FACES_SERVLET_NAME = FacesServlet.class.getName(); 085 private static final String SCHEMA_LOCATION_URL = "http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd"; 086 private static final String VERSION = "1.2"; 087 088 089 public MyFacesModuleBuilderExtension(Environment defaultEnvironment, AbstractNameQuery providerFactoryNameQuery, NamingBuilder namingBuilders) { 090 this.defaultEnvironment = defaultEnvironment; 091 this.providerFactoryNameQuery = providerFactoryNameQuery; 092 this.namingBuilders = namingBuilders; 093 } 094 095 public void createModule(Module module, Object plan, JarFile moduleFile, String targetPath, URL specDDUrl, Environment environment, Object moduleContextInfo, AbstractName earName, Naming naming, ModuleIDBuilder idBuilder) throws DeploymentException { 096 if (!(module instanceof WebModule)) { 097 //not a web module, nothing to do 098 return; 099 } 100 WebModule webModule = (WebModule) module; 101 WebAppType webApp = (WebAppType) webModule.getSpecDD(); 102 if (!hasFacesServlet(webApp)) { 103 return; 104 } 105 106 EnvironmentBuilder.mergeEnvironments(environment, defaultEnvironment); 107 } 108 109 public void installModule(JarFile earFile, EARContext earContext, Module module, Collection configurationStores, ConfigurationStore targetConfigurationStore, Collection repository) throws DeploymentException { 110 } 111 112 public void initContext(EARContext earContext, Module module, ClassLoader cl) throws DeploymentException { 113 } 114 115 public void addGBeans(EARContext earContext, Module module, ClassLoader cl, Collection repository) throws DeploymentException { 116 if (!(module instanceof WebModule)) { 117 //not a web module, nothing to do 118 return; 119 } 120 WebModule webModule = (WebModule) module; 121 WebAppType webApp = (WebAppType) webModule.getSpecDD(); 122 if (!hasFacesServlet(webApp)) { 123 return; 124 } 125 126 EARContext moduleContext = module.getEarContext(); 127 Map sharedContext = module.getSharedContext(); 128 //add the ServletContextListener to the web app context 129 GBeanData webAppData = (GBeanData) sharedContext.get(WebModule.WEB_APP_DATA); 130 //jetty specific support 131 Object value = webAppData.getAttribute("listenerClassNames"); 132 if (value instanceof Collection && !((Collection) value).contains(CONTEXT_LISTENER_NAME)) { 133 ((Collection<String>) value).add(CONTEXT_LISTENER_NAME); 134 } else { 135 //try to add listener to the web app xml 136 ListenerType listenerType = webApp.addNewListener(); 137 FullyQualifiedClassType className = listenerType.addNewListenerClass(); 138 className.setStringValue(CONTEXT_LISTENER_NAME); 139 } 140 AbstractName moduleName = moduleContext.getModuleName(); 141 Map<NamingBuilder.Key, Object> buildingContext = new HashMap<NamingBuilder.Key, Object>(); 142 buildingContext.put(NamingBuilder.GBEAN_NAME_KEY, moduleName); 143 144 //use the same jndi context as the web app 145 Map compContext = NamingBuilder.JNDI_KEY.get(module.getSharedContext()); 146 buildingContext.put(NamingBuilder.JNDI_KEY, compContext); 147 148 //use the same holder object as the web app. 149 Holder holder = NamingBuilder.INJECTION_KEY.get(sharedContext); 150 buildingContext.put(NamingBuilder.INJECTION_KEY, holder); 151 152 XmlObject jettyWebApp = webModule.getVendorDD(); 153 154 Configuration earConfiguration = earContext.getConfiguration(); 155 156 ClassFinder classFinder = createMyFacesClassFinder(webApp, webModule); 157 webModule.setClassFinder(classFinder); 158 159 namingBuilders.buildNaming(webApp, jettyWebApp, webModule, buildingContext); 160 161 AbstractName providerName = moduleContext.getNaming().createChildName(moduleName, "jsf-lifecycle", "jsf"); 162 GBeanData providerData = new GBeanData(providerName, LifecycleProviderGBean.GBEAN_INFO); 163 providerData.setAttribute("holder", holder); 164 providerData.setAttribute("componentContext", compContext); 165 providerData.setReferencePattern("LifecycleProviderFactory", providerFactoryNameQuery); 166 try { 167 moduleContext.addGBean(providerData); 168 } catch (GBeanAlreadyExistsException e) { 169 throw new DeploymentException("Duplicate jsf config gbean in web module", e); 170 } 171 172 //make the web app start second after the injection machinery 173 webAppData.addDependency(providerName); 174 175 } 176 177 private boolean hasFacesServlet(WebAppType webApp) { 178 for (ServletType servlet : webApp.getServletArray()) { 179 if (servlet.isSetServletClass() && FACES_SERVLET_NAME.equals(servlet.getServletClass().getStringValue().trim())) { 180 return true; 181 } 182 } 183 return false; 184 } 185 186 187 protected ClassFinder createMyFacesClassFinder(WebAppType webApp, WebModule webModule) throws DeploymentException { 188 189 List<Class> classes = getFacesClasses(webApp, webModule); 190 return new ClassFinder(classes); 191 } 192 193 194 /** 195 * getFacesConfigFileURL() 196 * <p/> 197 * <p>Locations to search for the MyFaces configuration file(s): 198 * <ol> 199 * <li>META-INF/faces-config.xml 200 * <li>WEB-INF/faces-config.xml 201 * <li>javax.faces.CONFIG_FILES -- Context initialization param of Comma separated 202 * list of URIs of (additional) faces config files 203 * </ol> 204 * <p/> 205 * <p><strong>Notes:</strong> 206 * <ul> 207 * </ul> 208 * 209 * @param webApp spec DD for module 210 * @param webModule module being deployed 211 * @return list of all managed bean classes from all faces-config xml files. 212 * @throws org.apache.geronimo.common.DeploymentException 213 * if a faces-config.xml file is located but cannot be parsed. 214 */ 215 private List<Class> getFacesClasses(WebAppType webApp, WebModule webModule) throws DeploymentException { 216 log.debug("getFacesClasses( " + webApp.toString() + "," + '\n' + 217 (webModule != null ? webModule.getName() : null) + " ): Entry"); 218 219 // Get the classloader from the module's EARContext 220 ClassLoader classLoader = webModule.getEarContext().getClassLoader(); 221 222 // 1. META-INF/faces-config.xml 223 List<Class> classes = new ArrayList<Class>(); 224 try { 225 URL url = DeploymentUtil.createJarURL(webModule.getModuleFile(), "META-INF/faces-config.xml"); 226 parseConfigFile(url, classLoader, classes); 227 } catch (MalformedURLException mfe) { 228 throw new DeploymentException("Could not locate META-INF/faces-config.xml" + mfe.getMessage(), mfe); 229 } 230 231 // 2. WEB-INF/faces-config.xml 232 try { 233 URL url = DeploymentUtil.createJarURL(webModule.getModuleFile(), "WEB-INF/faces-config.xml"); 234 parseConfigFile(url, classLoader, classes); 235 } catch (MalformedURLException mfe) { 236 throw new DeploymentException("Could not locate WEB-INF/faces-config.xml" + mfe.getMessage(), mfe); 237 } 238 239 // 3. javax.faces.CONFIG_FILES 240 ParamValueType[] paramValues = webApp.getContextParamArray(); 241 for (ParamValueType paramValue : paramValues) { 242 if (paramValue.getParamName().getStringValue().trim().equals("javax.faces.CONFIG_FILES")) { 243 String configFiles = paramValue.getParamValue().getStringValue().trim(); 244 StringTokenizer st = new StringTokenizer(configFiles, ",", false); 245 while (st.hasMoreTokens()) { 246 String configfile = st.nextToken().trim(); 247 if (!configfile.equals("")) { 248 if (configfile.startsWith("/")) { 249 configfile = configfile.substring(1); 250 } 251 try { 252 URL url = DeploymentUtil.createJarURL(webModule.getModuleFile(), configfile); 253 parseConfigFile(url, classLoader, classes); 254 } catch (MalformedURLException mfe) { 255 throw new DeploymentException("Could not locate config file " + configfile + ", " + mfe.getMessage(), mfe); 256 } 257 } 258 } 259 break; 260 } 261 } 262 263 log.debug("getFacesClasses() Exit: " + classes.size() + " " + classes.toString()); 264 return classes; 265 } 266 267 private void parseConfigFile(URL url, ClassLoader classLoader, List<Class> classes) throws DeploymentException { 268 log.debug("parseConfigFile( " + url.toString() + " ): Entry"); 269 270 try { 271 XmlObject xml = XmlBeansUtil.parse(url, null); 272 FacesConfigDocument fcd = convertToFacesConfigSchema(xml); 273 FacesConfigType facesConfig = fcd.getFacesConfig(); 274 275 // Get all the managed beans from the faces configuration file 276 FacesConfigManagedBeanType[] managedBeans = facesConfig.getManagedBeanArray(); 277 for (FacesConfigManagedBeanType managedBean : managedBeans) { 278 FullyQualifiedClassType cls = managedBean.getManagedBeanClass(); 279 String className = cls.getStringValue().trim(); 280 Class<?> clas; 281 try { 282 clas = classLoader.loadClass(className); 283 classes.add(clas); 284 } 285 catch (ClassNotFoundException e) { 286 log.warn("MyFacesModuleBuilderExtension: Could not load managed bean class: " + className + " mentioned in faces-config.xml file at " + url.toString()); 287 } 288 } 289 } 290 catch (XmlException xmle) { 291 throw new DeploymentException("Could not parse alleged faces-config.xml at " + url.toString(), xmle); 292 } 293 catch (IOException ioe) { 294 //config file does not exist 295 } 296 297 log.debug("parseConfigFile(): Exit"); 298 } 299 300 protected static FacesConfigDocument convertToFacesConfigSchema(XmlObject xmlObject) throws XmlException { 301 log.debug("convertToFacesConfigSchema( " + xmlObject.toString() + " ): Entry"); 302 XmlCursor cursor = xmlObject.newCursor(); 303 try { 304 cursor.toStartDoc(); 305 cursor.toFirstChild(); 306 if (SchemaConversionUtils.JAVAEE_NAMESPACE.equals(cursor.getName().getNamespaceURI())) { 307 //do nothing 308 } else if (SchemaConversionUtils.J2EE_NAMESPACE.equals(cursor.getName().getNamespaceURI())) { 309 SchemaConversionUtils.convertSchemaVersion(cursor, SchemaConversionUtils.JAVAEE_NAMESPACE, SCHEMA_LOCATION_URL, VERSION); 310 } else { 311 // otherwise assume DTD 312 SchemaConversionUtils.convertToSchema(cursor, SchemaConversionUtils.JAVAEE_NAMESPACE, SCHEMA_LOCATION_URL, VERSION); 313 } 314 } 315 finally { 316 cursor.dispose(); 317 } 318 XmlObject result = xmlObject.changeType(FacesConfigDocument.type); 319 if (result != null) { 320 XmlBeansUtil.validateDD(result); 321 log.debug("convertToFacesConfigSchema(): Exit 2" ); 322 return(FacesConfigDocument) result; 323 } 324 XmlBeansUtil.validateDD(xmlObject); 325 log.debug("convertToFacesConfigSchema(): Exit 3" ); 326 return(FacesConfigDocument) xmlObject; 327 } 328 329 public static final GBeanInfo GBEAN_INFO; 330 331 static { 332 GBeanInfoBuilder infoBuilder = GBeanInfoBuilder.createStatic(MyFacesModuleBuilderExtension.class, NameFactory.MODULE_BUILDER); 333 infoBuilder.addAttribute("defaultEnvironment", Environment.class, true, true); 334 infoBuilder.addAttribute("providerFactoryNameQuery", AbstractNameQuery.class, true, true); 335 infoBuilder.addReference("NamingBuilders", NamingBuilder.class, NameFactory.MODULE_BUILDER); 336 337 infoBuilder.setConstructor(new String[]{ 338 "defaultEnvironment", 339 "providerFactoryNameQuery", 340 "NamingBuilders"}); 341 GBEAN_INFO = infoBuilder.getBeanInfo(); 342 } 343 344 public static GBeanInfo getGBeanInfo() { 345 return GBEAN_INFO; 346 } 347 348 349 }