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 }