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