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.web25.deployment;
019    
020    import java.io.File;
021    import java.io.IOException;
022    import java.net.URI;
023    import java.net.URISyntaxException;
024    import java.net.URL;
025    import java.security.Permission;
026    import java.security.PermissionCollection;
027    import java.security.Permissions;
028    import java.util.ArrayList;
029    import java.util.Collection;
030    import java.util.Collections;
031    import java.util.Enumeration;
032    import java.util.HashMap;
033    import java.util.HashSet;
034    import java.util.LinkedList;
035    import java.util.List;
036    import java.util.Map;
037    import java.util.Set;
038    import java.util.LinkedHashSet;
039    import java.util.jar.JarEntry;
040    import java.util.jar.JarFile;
041    import java.util.zip.ZipEntry;
042    
043    import javax.security.jacc.WebResourcePermission;
044    import javax.security.jacc.WebRoleRefPermission;
045    import javax.security.jacc.WebUserDataPermission;
046    import javax.xml.namespace.QName;
047    
048    import org.apache.commons.logging.Log;
049    import org.apache.commons.logging.LogFactory;
050    import org.apache.geronimo.common.DeploymentException;
051    import org.apache.geronimo.deployment.ModuleIDBuilder;
052    import org.apache.geronimo.deployment.NamespaceDrivenBuilderCollection;
053    import org.apache.geronimo.deployment.ClassPathList;
054    import org.apache.geronimo.deployment.ModuleList;
055    import org.apache.geronimo.deployment.util.DeploymentUtil;
056    import org.apache.geronimo.deployment.xbeans.ServiceDocument;
057    import org.apache.geronimo.deployment.xmlbeans.XmlBeansUtil;
058    import org.apache.geronimo.gbean.AbstractName;
059    import org.apache.geronimo.gbean.AbstractNameQuery;
060    import org.apache.geronimo.gbean.GBeanData;
061    import org.apache.geronimo.j2ee.annotation.Holder;
062    import org.apache.geronimo.j2ee.deployment.EARContext;
063    import org.apache.geronimo.j2ee.deployment.Module;
064    import org.apache.geronimo.j2ee.deployment.ModuleBuilder;
065    import org.apache.geronimo.j2ee.deployment.ModuleBuilderExtension;
066    import org.apache.geronimo.j2ee.deployment.NamingBuilder;
067    import org.apache.geronimo.j2ee.deployment.WebModule;
068    import org.apache.geronimo.j2ee.deployment.WebServiceBuilder;
069    import org.apache.geronimo.j2ee.deployment.annotation.SecurityAnnotationHelper;
070    import org.apache.geronimo.j2ee.j2eeobjectnames.NameFactory;
071    import org.apache.geronimo.kernel.Kernel;
072    import org.apache.geronimo.kernel.Naming;
073    import org.apache.geronimo.kernel.config.Configuration;
074    import org.apache.geronimo.kernel.config.ConfigurationModuleType;
075    import org.apache.geronimo.kernel.config.ConfigurationStore;
076    import org.apache.geronimo.kernel.repository.Artifact;
077    import org.apache.geronimo.kernel.repository.Environment;
078    import org.apache.geronimo.kernel.repository.ImportType;
079    import org.apache.geronimo.naming.deployment.ResourceEnvironmentSetter;
080    import org.apache.geronimo.schema.SchemaConversionUtils;
081    import org.apache.geronimo.security.jacc.ComponentPermissions;
082    import org.apache.geronimo.security.util.HTTPMethods;
083    import org.apache.geronimo.security.util.URLPattern;
084    import org.apache.geronimo.xbeans.geronimo.j2ee.GerSecurityDocument;
085    import org.apache.geronimo.xbeans.javaee.FilterMappingType;
086    import org.apache.geronimo.xbeans.javaee.FilterType;
087    import org.apache.geronimo.xbeans.javaee.FullyQualifiedClassType;
088    import org.apache.geronimo.xbeans.javaee.ListenerType;
089    import org.apache.geronimo.xbeans.javaee.RoleNameType;
090    import org.apache.geronimo.xbeans.javaee.SecurityConstraintType;
091    import org.apache.geronimo.xbeans.javaee.SecurityRoleRefType;
092    import org.apache.geronimo.xbeans.javaee.SecurityRoleType;
093    import org.apache.geronimo.xbeans.javaee.ServletMappingType;
094    import org.apache.geronimo.xbeans.javaee.ServletType;
095    import org.apache.geronimo.xbeans.javaee.UrlPatternType;
096    import org.apache.geronimo.xbeans.javaee.WebAppDocument;
097    import org.apache.geronimo.xbeans.javaee.WebAppType;
098    import org.apache.geronimo.xbeans.javaee.WebResourceCollectionType;
099    import org.apache.xbean.finder.ClassFinder;
100    import org.apache.xmlbeans.XmlCursor;
101    import org.apache.xmlbeans.XmlDocumentProperties;
102    import org.apache.xmlbeans.XmlException;
103    import org.apache.xmlbeans.XmlObject;
104    
105    /**
106     * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
107     */
108    public abstract class AbstractWebModuleBuilder implements ModuleBuilder {
109        private static final Log log = LogFactory.getLog(AbstractWebModuleBuilder.class);
110    
111        private static final QName TAGLIB = new QName(SchemaConversionUtils.JAVAEE_NAMESPACE, "taglib");
112    
113        private static final String LINE_SEP = System.getProperty("line.separator");
114    
115        protected static final AbstractNameQuery MANAGED_CONNECTION_FACTORY_PATTERN;
116        private static final AbstractNameQuery ADMIN_OBJECT_PATTERN;
117        protected static final AbstractNameQuery STATELESS_SESSION_BEAN_PATTERN;
118        protected static final AbstractNameQuery STATEFUL_SESSION_BEAN_PATTERN;
119        protected static final AbstractNameQuery ENTITY_BEAN_PATTERN;
120        protected final Kernel kernel;
121        protected final NamespaceDrivenBuilderCollection securityBuilders;
122        protected final NamespaceDrivenBuilderCollection serviceBuilders;
123        protected final ResourceEnvironmentSetter resourceEnvironmentSetter;
124        protected final Collection<WebServiceBuilder> webServiceBuilder;
125    
126        protected final NamingBuilder namingBuilders;
127        protected final Collection<ModuleBuilderExtension> moduleBuilderExtensions;
128    
129        private static final QName SECURITY_QNAME = GerSecurityDocument.type.getDocumentElementName();
130        private static final QName SERVICE_QNAME = ServiceDocument.type.getDocumentElementName();
131    
132        /**
133         * Manifest classpath entries in a war configuration must be resolved relative to the war configuration, not the
134         * enclosing ear configuration.  Resolving relative to he war configuration using this offset produces the same
135         * effect as URI.create(module.targetPath()).resolve(mcpEntry) executed in the ear configuration.
136         */
137        private static final URI RELATIVE_MODULE_BASE_URI = URI.create("../");
138    
139        protected AbstractWebModuleBuilder(Kernel kernel, Collection securityBuilders, Collection serviceBuilders, NamingBuilder namingBuilders, ResourceEnvironmentSetter resourceEnvironmentSetter, Collection<WebServiceBuilder> webServiceBuilder, Collection<ModuleBuilderExtension> moduleBuilderExtensions) {
140            this.kernel = kernel;
141            this.securityBuilders = new NamespaceDrivenBuilderCollection(securityBuilders, SECURITY_QNAME);
142            this.serviceBuilders = new NamespaceDrivenBuilderCollection(serviceBuilders, SERVICE_QNAME);
143            this.namingBuilders = namingBuilders;
144            this.resourceEnvironmentSetter = resourceEnvironmentSetter;
145            this.webServiceBuilder = webServiceBuilder;
146            this.moduleBuilderExtensions = moduleBuilderExtensions == null? new ArrayList<ModuleBuilderExtension>(): moduleBuilderExtensions;
147        }
148    
149        static {
150            MANAGED_CONNECTION_FACTORY_PATTERN = new AbstractNameQuery(null, Collections.singletonMap(NameFactory.J2EE_TYPE, NameFactory.JCA_MANAGED_CONNECTION_FACTORY));
151            ADMIN_OBJECT_PATTERN = new AbstractNameQuery(null, Collections.singletonMap(NameFactory.J2EE_TYPE, NameFactory.JCA_ADMIN_OBJECT));
152            STATELESS_SESSION_BEAN_PATTERN = new AbstractNameQuery(null, Collections.singletonMap(NameFactory.J2EE_TYPE, NameFactory.STATELESS_SESSION_BEAN));
153            STATEFUL_SESSION_BEAN_PATTERN = new AbstractNameQuery(null, Collections.singletonMap(NameFactory.J2EE_TYPE, NameFactory.STATEFUL_SESSION_BEAN));
154            ENTITY_BEAN_PATTERN = new AbstractNameQuery(null, Collections.singletonMap(NameFactory.J2EE_TYPE, NameFactory.ENTITY_BEAN));
155    
156        }
157    
158        public NamingBuilder getNamingBuilders() {
159            return namingBuilders;
160        }
161    
162        protected void addGBeanDependencies(EARContext earContext, GBeanData webModuleData) {
163            Configuration earConfiguration = earContext.getConfiguration();
164            addDependencies(earContext.findGBeanDatas(earConfiguration, MANAGED_CONNECTION_FACTORY_PATTERN), webModuleData);
165            addDependencies(earContext.findGBeanDatas(earConfiguration, ADMIN_OBJECT_PATTERN), webModuleData);
166            addDependencies(earContext.findGBeanDatas(earConfiguration, STATELESS_SESSION_BEAN_PATTERN), webModuleData);
167            addDependencies(earContext.findGBeanDatas(earConfiguration, STATEFUL_SESSION_BEAN_PATTERN), webModuleData);
168            addDependencies(earContext.findGBeanDatas(earConfiguration, ENTITY_BEAN_PATTERN), webModuleData);
169        }
170    
171        private void addDependencies(LinkedHashSet<GBeanData> dependencies, GBeanData webModuleData) {
172            for (GBeanData dependency: dependencies) {
173                AbstractName dependencyName = dependency.getAbstractName();
174                webModuleData.addDependency(dependencyName);
175            }
176        }
177    
178        public Module createModule(File plan, JarFile moduleFile, Naming naming, ModuleIDBuilder idBuilder) throws DeploymentException {
179            return createModule(plan, moduleFile, ".", null, true, null, null, naming, idBuilder);
180        }
181    
182        public Module createModule(Object plan, JarFile moduleFile, String targetPath, URL specDDUrl, Environment environment, Object moduleContextInfo, AbstractName earName, Naming naming, ModuleIDBuilder idBuilder) throws DeploymentException {
183            return createModule(plan, moduleFile, targetPath, specDDUrl, false, (String) moduleContextInfo, earName, naming, idBuilder);
184        }
185    
186        protected abstract Module createModule(Object plan, JarFile moduleFile, String targetPath, URL specDDUrl, boolean standAlone, String contextRoot, AbstractName earName, Naming naming, ModuleIDBuilder idBuilder) throws DeploymentException;
187    
188        /**
189         * Some servlets will have multiple url patterns.  However, webservice servlets
190         * will only have one, which is what this method is intended for.
191         *
192         * @param webApp      spec deployment descriptor
193         * @param contextRoot context root for web app from application.xml or geronimo plan
194         * @return map of servlet names to path mapped to them.  Possibly inaccurate except for web services.
195         */
196        protected Map<String, String> buildServletNameToPathMap(WebAppType webApp, String contextRoot) {
197            if (contextRoot == null) {
198                contextRoot = "";
199            } else if (!contextRoot.startsWith("/")) {        
200                contextRoot = "/" + contextRoot;
201            }
202            Map<String, String> map = new HashMap<String, String>();
203            ServletMappingType[] servletMappings = webApp.getServletMappingArray();
204            for (ServletMappingType servletMapping : servletMappings) {
205                String servletName = servletMapping.getServletName().getStringValue().trim();
206                UrlPatternType[] urlPatterns = servletMapping.getUrlPatternArray();
207    
208                for (int i = 0; urlPatterns != null && (i < urlPatterns.length); i++) {
209                    map.put(servletName, contextRoot + urlPatterns[i].getStringValue().trim());
210                }
211            }
212            return map;
213        }
214    
215        protected String determineDefaultContextRoot(WebAppType webApp, boolean isStandAlone, JarFile moduleFile, String targetPath) {
216    
217            if (webApp != null && webApp.getId() != null) {
218                return webApp.getId();
219            }
220    
221            if (isStandAlone) {
222                // default configId is based on the moduleFile name
223                return "/" + trimPath(new File(moduleFile.getName()).getName());
224            }
225    
226            // default configId is based on the module uri from the application.xml
227            return trimPath(targetPath);
228        }
229    
230        private String trimPath(String path) {
231    
232            if (path == null) {
233                return null;
234            }
235    
236            if (path.endsWith(".war")) {
237                path = path.substring(0, path.length() - 4);
238            }
239            if (path.endsWith("/")) {
240                path = path.substring(0, path.length() - 1);
241            }
242    
243            return path;
244        }
245    
246        public void installModule(JarFile earFile, EARContext earContext, Module module, Collection configurationStores, ConfigurationStore targetConfigurationStore, Collection repositories) throws DeploymentException {
247            EARContext moduleContext;
248            if (module.isStandAlone()) {
249                moduleContext = earContext;
250            } else {
251                Environment environment = module.getEnvironment();
252                Artifact earConfigId = earContext.getConfigID();
253                Artifact configId = new Artifact(earConfigId.getGroupId(), earConfigId.getArtifactId() + "_" + module.getTargetPath(), earConfigId.getVersion(), "car");
254                environment.setConfigId(configId);
255                environment.addDependency(earConfigId, ImportType.ALL);
256                File configurationDir = new File(earContext.getBaseDir(), module.getTargetPath());
257                configurationDir.mkdirs();
258    
259                // construct the web app deployment context... this is the same class used by the ear context
260                try {
261                    File inPlaceConfigurationDir = null;
262                    if (null != earContext.getInPlaceConfigurationDir()) {
263                        inPlaceConfigurationDir = new File(earContext.getInPlaceConfigurationDir(), module.getTargetPath());
264                    }
265                    moduleContext = new EARContext(configurationDir,
266                            inPlaceConfigurationDir,
267                            environment,
268                            ConfigurationModuleType.WAR,
269                            module.getModuleName(),
270                            earContext);
271                } catch (DeploymentException e) {
272                    cleanupConfigurationDir(configurationDir);
273                    throw e;
274                }
275            }
276            module.setEarContext(moduleContext);
277            module.setRootEarContext(earContext);
278    
279            try {
280                ClassPathList manifestcp = new ClassPathList();
281                // add the warfile's content to the configuration
282                JarFile warFile = module.getModuleFile();
283                Enumeration<JarEntry> entries = warFile.entries();
284                List<ZipEntry> libs = new ArrayList<ZipEntry>();
285                while (entries.hasMoreElements()) {
286                    ZipEntry entry = entries.nextElement();
287                    URI targetPath = new URI(null, entry.getName(), null);
288                    if (entry.getName().equals("WEB-INF/web.xml")) {
289                        moduleContext.addFile(targetPath, module.getOriginalSpecDD());
290                    } else if (entry.getName().startsWith("WEB-INF/lib") && entry.getName().endsWith(".jar")) {
291                        // keep a collection of all libs in the war
292                        // libs must be installed after WEB-INF/classes which must be installed after this copy phase
293                        libs.add(entry);
294                    } else {
295                        moduleContext.addFile(targetPath, warFile, entry);
296                    }
297                }
298    
299                // always add WEB-INF/classes to the classpath regardless of whether
300                // any classes exist.  This must be searched BEFORE the WEB-INF/lib jar files,
301                // per the servlet specifications.
302                moduleContext.getConfiguration().addToClassPath("WEB-INF/classes/");
303                manifestcp.add("WEB-INF/classes/");
304    
305                // install the libs
306                for (ZipEntry entry : libs) {
307                    URI targetPath = new URI(null, entry.getName(), null);
308                    moduleContext.addInclude(targetPath, warFile, entry);
309                    manifestcp.add(entry.getName());
310                }
311    
312                // add the manifest classpath entries declared in the war to the class loader
313                // we have to explicitly add these since we are unpacking the web module
314                // and the url class loader will not pick up a manifest from an unpacked dir
315                moduleContext.addManifestClassPath(warFile, RELATIVE_MODULE_BASE_URI);
316                moduleContext.getGeneralData().put(ClassPathList.class, manifestcp);
317    
318            } catch (IOException e) {
319                throw new DeploymentException("Problem deploying war", e);
320            } catch (URISyntaxException e) {
321                throw new DeploymentException("Could not construct URI for location of war entry", e);
322            } finally {
323                if (!module.isStandAlone()) {
324                    try {
325                        moduleContext.flush();
326                    } catch (IOException e) {
327                        throw new DeploymentException("Problem closing war context", e);
328                    }
329                }
330            }
331            for (ModuleBuilderExtension mbe: moduleBuilderExtensions) {
332                mbe.installModule(earFile, earContext, module, configurationStores, targetConfigurationStore, repositories);
333            }
334        }
335    
336        protected void basicInitContext(EARContext earContext, Module module, XmlObject gerWebApp, boolean hasSecurityRealmName) throws DeploymentException {
337            //complete manifest classpath
338            EARContext moduleContext = module.getEarContext();
339            ClassPathList manifestcp = (ClassPathList) moduleContext.getGeneralData().get(ClassPathList.class);
340            ModuleList moduleLocations = (ModuleList) module.getRootEarContext().getGeneralData().get(ModuleList.class);
341            URI baseUri = URI.create(module.getTargetPath());
342            URI resolutionUri = invertURI(baseUri);
343            earContext.getCompleteManifestClassPath(module.getModuleFile(), baseUri, resolutionUri, manifestcp, moduleLocations);
344    
345    
346            WebAppType webApp = (WebAppType) module.getSpecDD();
347            if ((webApp.getSecurityConstraintArray().length > 0 || webApp.getSecurityRoleArray().length > 0) &&
348                    !hasSecurityRealmName) {
349                throw new DeploymentException("web.xml for web app " + module.getName() + " includes security elements but Geronimo deployment plan is not provided or does not contain <security-realm-name> element necessary to configure security accordingly.");
350            }
351            XmlObject[] securityElements = XmlBeansUtil.selectSubstitutionGroupElements(SECURITY_QNAME, gerWebApp);
352            if (securityElements.length > 0 && !hasSecurityRealmName) {
353                throw new DeploymentException("You have supplied a security configuration for web app " + module.getName() + " but no security-realm-name to allow login");
354            }
355            getNamingBuilders().buildEnvironment(webApp, module.getVendorDD(), module.getEnvironment());
356            //this is silly
357            getNamingBuilders().initContext(webApp, gerWebApp, module);
358    
359            Map servletNameToPathMap = buildServletNameToPathMap((WebAppType) module.getSpecDD(), ((WebModule) module).getContextRoot());
360    
361            Map sharedContext = module.getSharedContext();
362            for (Object aWebServiceBuilder : webServiceBuilder) {
363                WebServiceBuilder serviceBuilder = (WebServiceBuilder) aWebServiceBuilder;
364                serviceBuilder.findWebServices(module, false, servletNameToPathMap, module.getEnvironment(), sharedContext);
365            }
366            securityBuilders.build(gerWebApp, earContext, module.getEarContext());
367            serviceBuilders.build(gerWebApp, earContext, module.getEarContext());
368        }
369    
370        static URI invertURI(URI baseUri) {
371            URI resolutionUri = URI.create(".");
372            for (URI test = baseUri; !test.equals(RELATIVE_MODULE_BASE_URI); test = test.resolve(RELATIVE_MODULE_BASE_URI)) {
373                resolutionUri = resolutionUri.resolve(RELATIVE_MODULE_BASE_URI);
374            }
375            return resolutionUri;
376        }
377    
378        protected WebAppDocument convertToServletSchema(XmlObject xmlObject) throws XmlException {
379    
380            String schemaLocationURL = "http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd";
381            String version = "2.5";
382            XmlCursor cursor = xmlObject.newCursor();
383            try {
384                cursor.toStartDoc();
385                cursor.toFirstChild();
386                if ("http://java.sun.com/xml/ns/j2ee".equals(cursor.getName().getNamespaceURI())) {
387                    SchemaConversionUtils.convertSchemaVersion(cursor, SchemaConversionUtils.JAVAEE_NAMESPACE, schemaLocationURL, version);
388                    XmlObject result = xmlObject.changeType(WebAppDocument.type);
389                    XmlBeansUtil.validateDD(result);
390                    return (WebAppDocument) result;
391                }
392    
393                if ("http://java.sun.com/xml/ns/javaee".equals(cursor.getName().getNamespaceURI())) {
394                    SchemaConversionUtils.convertSchemaVersion(cursor, SchemaConversionUtils.JAVAEE_NAMESPACE, schemaLocationURL, version);
395                    XmlObject result = xmlObject.changeType(WebAppDocument.type);
396                    XmlBeansUtil.validateDD(result);
397                    return (WebAppDocument) result;
398                }
399    
400                //otherwise assume DTD
401                XmlDocumentProperties xmlDocumentProperties = cursor.documentProperties();
402                String publicId = xmlDocumentProperties.getDoctypePublicId();
403                boolean is22 = "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN".equals(publicId);
404                if ("-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN".equals(publicId) ||
405                        is22) {
406                    XmlCursor moveable = xmlObject.newCursor();
407                    try {
408                        moveable.toStartDoc();
409                        moveable.toFirstChild();
410    
411                        SchemaConversionUtils.convertToSchema(cursor, SchemaConversionUtils.JAVAEE_NAMESPACE, schemaLocationURL, version);
412                        cursor.toStartDoc();
413                        cursor.toChild(SchemaConversionUtils.JAVAEE_NAMESPACE, "web-app");
414                        cursor.toFirstChild();
415                        SchemaConversionUtils.convertToDescriptionGroup(SchemaConversionUtils.JAVAEE_NAMESPACE, cursor, moveable);
416                        SchemaConversionUtils.convertToJNDIEnvironmentRefsGroup(SchemaConversionUtils.JAVAEE_NAMESPACE, cursor, moveable);
417                        cursor.push();
418                        if (cursor.toNextSibling(TAGLIB)) {
419                            cursor.toPrevSibling();
420                            moveable.toCursor(cursor);
421                            cursor.beginElement("jsp-config", SchemaConversionUtils.JAVAEE_NAMESPACE);
422                            while (moveable.toNextSibling(TAGLIB)) {
423                                moveable.moveXml(cursor);
424                            }
425                        }
426                        cursor.pop();
427                        do {
428                            String name = cursor.getName().getLocalPart();
429                            if ("filter".equals(name) || "servlet".equals(name) || "context-param".equals(name)) {
430                                cursor.push();
431                                cursor.toFirstChild();
432                                SchemaConversionUtils.convertToDescriptionGroup(SchemaConversionUtils.JAVAEE_NAMESPACE, cursor, moveable);
433                                while (cursor.toNextSibling(SchemaConversionUtils.JAVAEE_NAMESPACE, "init-param")) {
434                                    cursor.push();
435                                    cursor.toFirstChild();
436                                    SchemaConversionUtils.convertToDescriptionGroup(SchemaConversionUtils.JAVAEE_NAMESPACE, cursor, moveable);
437                                    cursor.pop();
438                                }
439                                cursor.pop();
440                                cursor.push();
441                                if (cursor.toChild(SchemaConversionUtils.JAVAEE_NAMESPACE, "jsp-file")) {
442                                    String jspFile = cursor.getTextValue();
443                                    if (!jspFile.startsWith("/")){
444                                        if (is22) {
445                                            cursor.setTextValue("/" + jspFile);
446                                        } else {
447                                            throw new XmlException("jsp-file does not start with / and this is not a 2.2 web app: " + jspFile);
448                                        }
449                                    }
450                                }
451                                cursor.pop();
452                            }
453                        } while (cursor.toNextSibling());
454                    } finally {
455                        moveable.dispose();
456                    }
457                }
458            } finally {
459                cursor.dispose();
460            }
461            XmlObject result = xmlObject.changeType(WebAppDocument.type);
462            if (result != null) {
463                XmlBeansUtil.validateDD(result);
464                return (WebAppDocument) result;
465            }
466            XmlBeansUtil.validateDD(xmlObject);
467            return (WebAppDocument) xmlObject;
468        }
469    
470    
471        protected void addUnmappedJSPPermissions(Set<String> securityRoles, Map<String, PermissionCollection> rolePermissions) {
472            for (String roleName : securityRoles) {
473                addPermissionToRole(roleName, new WebRoleRefPermission("", roleName), rolePermissions);
474            }
475        }
476    
477        protected ComponentPermissions buildSpecSecurityConfig(WebAppType webApp, Set<String> securityRoles, Map<String, PermissionCollection> rolePermissions) {
478            Map<String, URLPattern> uncheckedPatterns = new HashMap<String, URLPattern>();
479            Map<UncheckedItem, HTTPMethods> uncheckedResourcePatterns = new HashMap<UncheckedItem, HTTPMethods>();
480            Map<UncheckedItem, HTTPMethods> uncheckedUserPatterns = new HashMap<UncheckedItem, HTTPMethods>();
481            Map<String, URLPattern> excludedPatterns = new HashMap<String, URLPattern>();
482            Map<String, URLPattern> rolesPatterns = new HashMap<String, URLPattern>();
483            Set<URLPattern> allSet = new HashSet<URLPattern>();   // == allMap.values()
484            Map<String, URLPattern> allMap = new HashMap<String, URLPattern>();   //uncheckedPatterns union excludedPatterns union rolesPatterns.
485    
486            SecurityConstraintType[] securityConstraintArray = webApp.getSecurityConstraintArray();
487            for (SecurityConstraintType securityConstraintType : securityConstraintArray) {
488                Map<String, URLPattern> currentPatterns;
489                if (securityConstraintType.isSetAuthConstraint()) {
490                    if (securityConstraintType.getAuthConstraint().getRoleNameArray().length == 0) {
491                        currentPatterns = excludedPatterns;
492                    } else {
493                        currentPatterns = rolesPatterns;
494                    }
495                } else {
496                    currentPatterns = uncheckedPatterns;
497                }
498    
499                String transport = "";
500                if (securityConstraintType.isSetUserDataConstraint()) {
501                    transport = securityConstraintType.getUserDataConstraint().getTransportGuarantee().getStringValue().trim().toUpperCase();
502                }
503    
504                WebResourceCollectionType[] webResourceCollectionTypeArray = securityConstraintType.getWebResourceCollectionArray();
505                for (WebResourceCollectionType webResourceCollectionType : webResourceCollectionTypeArray) {
506                    UrlPatternType[] urlPatternTypeArray = webResourceCollectionType.getUrlPatternArray();
507                    for (UrlPatternType urlPatternType : urlPatternTypeArray) {
508                        String url = urlPatternType.getStringValue().trim();
509                        URLPattern pattern = currentPatterns.get(url);
510                        if (pattern == null) {
511                            pattern = new URLPattern(url);
512                            currentPatterns.put(url, pattern);
513                        }
514    
515                        URLPattern allPattern = allMap.get(url);
516                        if (allPattern == null) {
517                            allPattern = new URLPattern(url);
518                            allSet.add(allPattern);
519                            allMap.put(url, allPattern);
520                        }
521    
522                        String[] httpMethodTypeArray = webResourceCollectionType.getHttpMethodArray();
523                        if (httpMethodTypeArray.length == 0) {
524                            pattern.addMethod("");
525                            allPattern.addMethod("");
526                        } else {
527                            for (String aHttpMethodTypeArray : httpMethodTypeArray) {
528                                String method = (aHttpMethodTypeArray == null ? null : aHttpMethodTypeArray.trim());
529                                if (method != null) {
530                                    pattern.addMethod(method);
531                                    allPattern.addMethod(method);
532                                }
533                            }
534                        }
535                        if (currentPatterns == rolesPatterns) {
536                            RoleNameType[] roleNameTypeArray = securityConstraintType.getAuthConstraint().getRoleNameArray();
537                            for (RoleNameType roleNameType : roleNameTypeArray) {
538                                String role = roleNameType.getStringValue().trim();
539                                if (role.equals("*")) {
540                                    pattern.addAllRoles(securityRoles);
541                                } else {
542                                    pattern.addRole(role);
543                                }
544                            }
545                        }
546    
547                        pattern.setTransport(transport);
548                    }
549                }
550            }
551    
552            PermissionCollection excludedPermissions = new Permissions();
553            PermissionCollection uncheckedPermissions = new Permissions();
554    
555            for (URLPattern pattern : excludedPatterns.values()) {
556                String name = pattern.getQualifiedPattern(allSet);
557                String actions = pattern.getMethods();
558    
559                excludedPermissions.add(new WebResourcePermission(name, actions));
560                excludedPermissions.add(new WebUserDataPermission(name, actions));
561            }
562    
563            for (URLPattern pattern : rolesPatterns.values()) {
564                String name = pattern.getQualifiedPattern(allSet);
565                String actions = pattern.getMethods();
566                WebResourcePermission permission = new WebResourcePermission(name, actions);
567    
568                for (String roleName : pattern.getRoles()) {
569                    addPermissionToRole(roleName, permission, rolePermissions);
570                }
571                HTTPMethods methods = pattern.getHTTPMethods();
572                int transportType = pattern.getTransport();
573    
574                addOrUpdatePattern(uncheckedUserPatterns, name, methods, transportType);
575            }
576    
577            for (URLPattern pattern : uncheckedPatterns.values()) {
578                String name = pattern.getQualifiedPattern(allSet);
579                HTTPMethods methods = pattern.getHTTPMethods();
580    
581                addOrUpdatePattern(uncheckedResourcePatterns, name, methods, URLPattern.NA);
582    
583                int transportType = pattern.getTransport();
584                addOrUpdatePattern(uncheckedUserPatterns, name, methods, transportType);
585            }
586    
587            /**
588             * A <code>WebResourcePermission</code> and a <code>WebUserDataPermission</code> must be instantiated for
589             * each <tt>url-pattern</tt> in the deployment descriptor and the default pattern "/", that is not combined
590             * by the <tt>web-resource-collection</tt> elements of the deployment descriptor with ever HTTP method
591             * value.  The permission objects must be contructed using the qualified pattern as their name and with
592             * actions defined by the subset of the HTTP methods that do not occur in combination with the pattern.
593             * The resulting permissions that must be added to the unchecked policy statements by calling the
594             * <code>addToUncheckedPolcy</code> method on the <code>PolicyConfiguration</code> object.
595             */
596            for (URLPattern pattern : allSet) {
597                String name = pattern.getQualifiedPattern(allSet);
598                HTTPMethods methods = pattern.getComplementedHTTPMethods();
599    
600                if (methods.isNone()) {
601                    continue;
602                }
603    
604                addOrUpdatePattern(uncheckedResourcePatterns, name, methods, URLPattern.NA);
605                addOrUpdatePattern(uncheckedUserPatterns, name, methods, URLPattern.NA);
606            }
607    
608            URLPattern pattern = new URLPattern("/");
609            if (!allSet.contains(pattern)) {
610                String name = pattern.getQualifiedPattern(allSet);
611                HTTPMethods methods = pattern.getComplementedHTTPMethods();
612    
613                addOrUpdatePattern(uncheckedResourcePatterns, name, methods, URLPattern.NA);
614                addOrUpdatePattern(uncheckedUserPatterns, name, methods, URLPattern.NA);
615            }
616    
617            //Create the uncheckedPermissions for WebResourcePermissions
618            for (UncheckedItem item : uncheckedResourcePatterns.keySet()) {
619                HTTPMethods methods = uncheckedResourcePatterns.get(item);
620                String actions = URLPattern.getMethodsWithTransport(methods, item.getTransportType());
621    
622                uncheckedPermissions.add(new WebResourcePermission(item.getName(), actions));
623            }
624            //Create the uncheckedPermissions for WebUserDataPermissions
625            for (UncheckedItem item : uncheckedUserPatterns.keySet()) {
626                HTTPMethods methods = uncheckedUserPatterns.get(item);
627                String actions = URLPattern.getMethodsWithTransport(methods, item.getTransportType());
628    
629                uncheckedPermissions.add(new WebUserDataPermission(item.getName(), actions));
630            }
631    
632            return new ComponentPermissions(excludedPermissions, uncheckedPermissions, rolePermissions);
633    
634        }
635    
636        protected void addPermissionToRole(String roleName, Permission permission, Map<String, PermissionCollection> rolePermissions) {
637            PermissionCollection permissionsForRole = rolePermissions.get(roleName);
638            if (permissionsForRole == null) {
639                permissionsForRole = new Permissions();
640                rolePermissions.put(roleName, permissionsForRole);
641            }
642            permissionsForRole.add(permission);
643        }
644    
645        private void addOrUpdatePattern(Map<UncheckedItem, HTTPMethods> patternMap, String name, HTTPMethods actions, int transportType) {
646            UncheckedItem item = new UncheckedItem(name, transportType);
647            HTTPMethods existingActions = patternMap.get(item);
648            if (existingActions != null) {
649                patternMap.put(item, existingActions.add(actions));
650                return;
651            }
652    
653            patternMap.put(item, new HTTPMethods(actions, false));
654        }
655    
656        protected static Set<String> collectRoleNames(WebAppType webApp) {
657            Set<String> roleNames = new HashSet<String>();
658    
659            SecurityRoleType[] securityRoles = webApp.getSecurityRoleArray();
660            for (SecurityRoleType securityRole : securityRoles) {
661                roleNames.add(securityRole.getRoleName().getStringValue().trim());
662            }
663    
664            return roleNames;
665        }
666    
667        protected static void check(WebAppType webApp) throws DeploymentException {
668            checkURLPattern(webApp);
669            checkMultiplicities(webApp);
670        }
671    
672        private static void checkURLPattern(WebAppType webApp) throws DeploymentException {
673    
674            FilterMappingType[] filterMappings = webApp.getFilterMappingArray();
675            for (FilterMappingType filterMapping : filterMappings) {
676                UrlPatternType[] urlPatterns = filterMapping.getUrlPatternArray();
677                for (int j = 0; (urlPatterns != null) && (j < urlPatterns.length); j++) {
678                    checkString(urlPatterns[j].getStringValue().trim());
679                }
680            }
681    
682            ServletMappingType[] servletMappings = webApp.getServletMappingArray();
683            for (ServletMappingType servletMapping : servletMappings) {
684                UrlPatternType[] urlPatterns = servletMapping.getUrlPatternArray();
685                for (int j = 0; (urlPatterns != null) && (j < urlPatterns.length); j++) {
686                    checkString(urlPatterns[j].getStringValue().trim());
687                }
688            }
689    
690            SecurityConstraintType[] constraints = webApp.getSecurityConstraintArray();
691            for (SecurityConstraintType constraint : constraints) {
692                WebResourceCollectionType[] collections = constraint.getWebResourceCollectionArray();
693                for (WebResourceCollectionType collection : collections) {
694                    UrlPatternType[] patterns = collection.getUrlPatternArray();
695                    for (UrlPatternType pattern : patterns) {
696                        checkString(pattern.getStringValue().trim());
697                    }
698                }
699            }
700        }
701    
702        protected static void checkString(String pattern) throws DeploymentException {
703            //j2ee_1_4.xsd explicitly requires preserving all whitespace. Do not trim.
704            if (pattern.indexOf(0x0D) >= 0) throw new DeploymentException("<url-pattern> must not contain CR(#xD)");
705            if (pattern.indexOf(0x0A) >= 0) throw new DeploymentException("<url-pattern> must not contain LF(#xA)");
706        }
707    
708        private static void checkMultiplicities(WebAppType webApp) throws DeploymentException {
709            if (webApp.getSessionConfigArray().length > 1)
710                throw new DeploymentException("Multiple <session-config> elements found");
711            if (webApp.getJspConfigArray().length > 1)
712                throw new DeploymentException("Multiple <jsp-config> elements found");
713            if (webApp.getLoginConfigArray().length > 1)
714                throw new DeploymentException("Multiple <login-config> elements found");
715        }
716    
717        private boolean cleanupConfigurationDir(File configurationDir) {
718            LinkedList<String> cannotBeDeletedList = new LinkedList<String>();
719    
720            if (!DeploymentUtil.recursiveDelete(configurationDir, cannotBeDeletedList)) {
721                // Output a message to help user track down file problem
722                log.warn("Unable to delete " + cannotBeDeletedList.size() +
723                        " files while recursively deleting directory "
724                        + configurationDir.getAbsolutePath() + LINE_SEP +
725                        "The first file that could not be deleted was:" + LINE_SEP + "  " +
726                        (!cannotBeDeletedList.isEmpty() ? cannotBeDeletedList.getFirst() : ""));
727                return false;
728            }
729            return true;
730        }
731    
732        protected void processRoleRefPermissions(ServletType servletType, Set<String> securityRoles, Map<String, PermissionCollection> rolePermissions) {
733            String servletName = servletType.getServletName().getStringValue().trim();
734            //WebRoleRefPermissions
735            SecurityRoleRefType[] securityRoleRefTypeArray = servletType.getSecurityRoleRefArray();
736            Set<String> unmappedRoles = new HashSet<String>(securityRoles);
737            for (SecurityRoleRefType securityRoleRefType : securityRoleRefTypeArray) {
738                String roleName = securityRoleRefType.getRoleName().getStringValue().trim();
739                String roleLink = securityRoleRefType.getRoleLink().getStringValue().trim();
740                //jacc 3.1.3.2
741                /*   The name of the WebRoleRefPermission must be the servlet-name in whose
742                * context the security-role-ref is defined. The actions of the  WebRoleRefPermission
743                * must be the value of the role-name (that is the  reference), appearing in the security-role-ref.
744                * The deployment tools must  call the addToRole method on the PolicyConfiguration object to add the
745                * WebRoleRefPermission object resulting from the translation to the role
746                * identified in the role-link appearing in the security-role-ref.
747                */
748                addPermissionToRole(roleLink, new WebRoleRefPermission(servletName, roleName), rolePermissions);
749                unmappedRoles.remove(roleName);
750            }
751            for (String roleName : unmappedRoles) {
752                addPermissionToRole(roleName, new WebRoleRefPermission(servletName, roleName), rolePermissions);
753            }
754        }
755    
756        protected ClassFinder createWebAppClassFinder(WebAppType webApp, WebModule webModule) throws DeploymentException {
757            // Get the classloader from the module's EARContext
758            ClassLoader classLoader = webModule.getEarContext().getClassLoader();
759            return createWebAppClassFinder(webApp, classLoader);
760        }
761    
762        public static ClassFinder createWebAppClassFinder(WebAppType webApp, ClassLoader classLoader) throws DeploymentException {
763            //------------------------------------------------------------------------------------
764            // Find the list of classes from the web.xml we want to search for annotations in
765            //------------------------------------------------------------------------------------
766            List<Class> classes = new ArrayList<Class>();
767    
768            // Get all the servlets from the deployment descriptor
769            ServletType[] servlets = webApp.getServletArray();
770            for (ServletType servlet : servlets) {
771                FullyQualifiedClassType cls = servlet.getServletClass();
772                if (cls != null) {                              // Don't try this for JSPs
773                    Class<?> clas;
774                    try {
775                        clas = classLoader.loadClass(cls.getStringValue());
776                    } catch (ClassNotFoundException e) {
777                        throw new DeploymentException("AbstractWebModuleBuilder: Could not load servlet class: " + cls.getStringValue(), e);
778                    }
779                    addClass(classes, clas);
780                }
781            }
782    
783            // Get all the listeners from the deployment descriptor
784            ListenerType[] listeners = webApp.getListenerArray();
785            for (ListenerType listener : listeners) {
786                FullyQualifiedClassType cls = listener.getListenerClass();
787                Class<?> clas;
788                try {
789                    clas = classLoader.loadClass(cls.getStringValue());
790                } catch (ClassNotFoundException e) {
791                    throw new DeploymentException("AbstractWebModuleBuilder: Could not load listener class: " + cls.getStringValue(), e);
792                }
793                addClass(classes, clas);
794            }
795    
796            // Get all the filters from the deployment descriptor
797            FilterType[] filters = webApp.getFilterArray();
798            for (FilterType filter : filters) {
799                FullyQualifiedClassType cls = filter.getFilterClass();
800                Class<?> clas;
801                try {
802                    clas = classLoader.loadClass(cls.getStringValue());
803                } catch (ClassNotFoundException e) {
804                    throw new DeploymentException("AbstractWebModuleBuilder: Could not load filter class: " + cls.getStringValue(), e);
805                }
806                addClass(classes, clas);
807            }
808    
809            // see https://issues.apache.org/jira/browse/GERONIMO-3421 .
810            // if the user has botched her classloader config (perhaps by
811            // not including a jar that her app needs) then ClassFinder
812            // will throw NoClassDefFoundError.  we want to indicate that
813            // it's the user's error and provide a little context to help
814            // her fix it.
815            try {
816                return new ClassFinder(classes);
817            } catch (NoClassDefFoundError e) {
818                throw new DeploymentException("Classloader for " + webApp.getId() + "can't find " + e.getMessage(), e);
819            }
820        }
821    
822        private static void addClass(List<Class> classes, Class<?> clas) {
823            while (clas != Object.class) {
824                classes.add(clas);
825                clas = clas.getSuperclass();
826            }
827        }
828    
829        protected void configureBasicWebModuleAttributes(WebAppType webApp, XmlObject vendorPlan, EARContext moduleContext, EARContext earContext, WebModule webModule, GBeanData webModuleData) throws DeploymentException {
830            Map<NamingBuilder.Key, Object> buildingContext = new HashMap<NamingBuilder.Key, Object>();
831            buildingContext.put(NamingBuilder.GBEAN_NAME_KEY, moduleContext.getModuleName());
832    
833            if (!webApp.getMetadataComplete()) {
834                // Create a classfinder and populate it for the naming builder(s). The absence of a
835                // classFinder in the module will convey whether metadata-complete is set (or not)
836                webModule.setClassFinder(createWebAppClassFinder(webApp, webModule));
837                SecurityAnnotationHelper.processAnnotations(webApp, webModule.getClassFinder());
838            }
839            //N.B. we use the ear context which has all the gbeans we could possibly be looking up from this ear.
840            //nope, persistence units can be in the war.
841            //This means that you cannot use the default environment of the web builder to add configs that will be searched.
842            getNamingBuilders().buildNaming(webApp, vendorPlan, webModule, buildingContext);
843    
844            Map compContext = NamingBuilder.JNDI_KEY.get(buildingContext);
845            Holder holder = NamingBuilder.INJECTION_KEY.get(buildingContext);
846    
847            webModule.getSharedContext().put(WebModule.WEB_APP_DATA, webModuleData);
848            webModule.getSharedContext().put(NamingBuilder.JNDI_KEY, compContext);
849            webModule.getSharedContext().put(NamingBuilder.INJECTION_KEY, holder);
850            if (moduleContext.getServerName() != null) {
851                webModuleData.setReferencePattern("J2EEServer", moduleContext.getServerName());
852            }
853            if (!webModule.isStandAlone()) {
854                webModuleData.setReferencePattern("J2EEApplication", earContext.getModuleName());
855            }
856    
857            webModuleData.setAttribute("holder", holder);
858    
859            //Add dependencies on managed connection factories and ejbs in this app
860            //This is overkill, but allows for people not using java:comp context (even though we don't support it)
861            //and sidesteps the problem of circular references between ejbs.
862            if (earContext != moduleContext) {
863                addGBeanDependencies(earContext, webModuleData);
864            }
865    
866            webModuleData.setAttribute("componentContext", compContext);
867            webModuleData.setReferencePattern("TransactionManager", moduleContext.getTransactionManagerName());
868            webModuleData.setReferencePattern("TrackedConnectionAssociator", moduleContext.getConnectionTrackerName());
869        }
870    
871        class UncheckedItem {
872            final static int NA = 0x00;
873            final static int INTEGRAL = 0x01;
874            final static int CONFIDENTIAL = 0x02;
875    
876            private int transportType = NA;
877            private String name;
878    
879            public UncheckedItem(String name, int transportType) {
880                setName(name);
881                setTransportType(transportType);
882            }
883    
884            public boolean equals(Object o) {
885                UncheckedItem item = (UncheckedItem) o;
886                return item.transportType == transportType && item.name.equals(this.name);
887            }
888    
889    
890            public int hashCode() {
891                return name.hashCode() + transportType;
892            }
893    
894            public String getName() {
895                return name;
896            }
897    
898            public void setName(String name) {
899                this.name = name;
900            }
901    
902            public int getTransportType() {
903                return transportType;
904            }
905    
906            public void setTransportType(int transportType) {
907                this.transportType = transportType;
908            }
909        }
910    }