001    // Copyright 2004, 2005 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    //     http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry.resolver;
016    
017    import org.apache.commons.logging.Log;
018    import org.apache.hivemind.ApplicationRuntimeException;
019    import org.apache.hivemind.ClassResolver;
020    import org.apache.hivemind.Location;
021    import org.apache.hivemind.Resource;
022    import org.apache.hivemind.impl.LocationImpl;
023    import org.apache.hivemind.util.ClasspathResource;
024    import org.apache.tapestry.INamespace;
025    import org.apache.tapestry.IRequestCycle;
026    import org.apache.tapestry.services.ClassFinder;
027    import org.apache.tapestry.spec.ComponentSpecification;
028    import org.apache.tapestry.spec.IComponentSpecification;
029    
030    /**
031     * Utility class that understands the rules of component types (which may optionally have a library
032     * prefix) and can resolve the type to a {@link org.apache.tapestry.INamespace}and a
033     * {@link org.apache.tapestry.spec.IComponentSpecification}.
034     * <p>
035     * Like {@link org.apache.tapestry.resolver.PageSpecificationResolver}, if the component is not
036     * defined explicitly in the namespace, a search may occur: Performs the tricky work of resolving a
037     * page name to a page specification. The search for pages in the application namespace is the most
038     * complicated, since Tapestry searches for pages that aren't explicitly defined in the application
039     * specification. The search, based on the <i>simple-name </i> of the page, goes as follows:
040     * <ul>
041     * <li>As declared in the application specification
042     * <li><i>type</i>.jwc in the same folder as the application specification
043     * <li><i>type</i> jwc in the WEB-INF/ <i>servlet-name </i> directory of the context root
044     * <li><i>type</i>.jwc in WEB-INF
045     * <li><i>type</i>.jwc in the application root (within the context root)
046     * <li>By searching the framework namespace
047     * <li>By searching for a named class file within the org.apache.tapestry.component-class-packages
048     * property (defined within the namespace)
049     * </ul>
050     *
051     * The search for components in library namespaces is more abbreviated:
052     * <ul>
053     * <li>As declared in the library specification
054     * <li><i>type </i>.jwc in the same folder as the library specification
055     * <li>By searching the framework namespace
056     * </ul>
057     *
058     * @since 3.0
059     */
060    
061    public class ComponentSpecificationResolverImpl extends AbstractSpecificationResolver implements ComponentSpecificationResolver
062    {
063        /** Set by container. */
064        private Log _log;
065    
066        /** Set by resolve(). */
067        private String _type;
068    
069        private ClassFinder _classFinder;
070    
071        private ClassResolver _classResolver;
072    
073        protected void reset()
074        {
075            _type = null;
076    
077            super.reset();
078        }
079    
080        /**
081         * Passed the namespace of a container (to resolve the type in) and the type to resolve,
082         * performs the processing. A "bare type" (without a library prefix) may be in the
083         * containerNamespace, or the framework namespace (a search occurs in that order).
084         *
085         * @param cycle
086         *            current request cycle
087         * @param containerNamespace
088         *            namespace that may contain a library referenced in the type
089         * @param type
090         *            the component specification to find, either a simple name, or prefixed with a
091         *            library id (defined for the container namespace)
092         * @see #getNamespace()
093         * @see #getSpecification()
094         */
095    
096        public void resolve(IRequestCycle cycle, INamespace containerNamespace, String type, Location location)
097        {
098            int colonx = type.indexOf(':');
099    
100            if (colonx > 0)
101            {
102                String libraryId = type.substring(0, colonx);
103                String simpleType = type.substring(colonx + 1);
104    
105                resolve(cycle, containerNamespace, libraryId, simpleType, location);
106            }
107            else
108                resolve(cycle, containerNamespace, null, type, location);
109    
110            IComponentSpecification spec = getSpecification();
111    
112            if (spec.isDeprecated())
113                _log.warn(ResolverMessages.componentIsDeprecated(type, location));
114        }
115    
116        /**
117         * Like
118         * {@link #resolve(org.apache.tapestry.IRequestCycle, org.apache.tapestry.INamespace, java.lang.String, Location)},
119         * but used when the type has already been parsed into a library id and a simple type.
120         *
121         * @param cycle
122         *            current request cycle
123         * @param containerNamespace
124         *            namespace that may contain a library referenced in the type
125         * @param libraryId
126         *            the library id within the container namespace, or null
127         * @param type
128         *            the component specification to find as a simple name (without a library prefix)
129         * @param location
130         *            of reference to be resolved
131         * @throws ApplicationRuntimeException
132         *             if the type cannot be resolved
133         */
134    
135        public void resolve(IRequestCycle cycle, INamespace containerNamespace, String libraryId,
136                            String type, Location location)
137        {
138            reset();
139            _type = type;
140    
141            INamespace namespace;
142            try
143            {
144                namespace = findNamespaceForId(containerNamespace, libraryId);
145            }
146            catch (ApplicationRuntimeException e)
147            {
148                throw new ApplicationRuntimeException(e.getMessage(), location, e);
149            }
150    
151            setNamespace(namespace);
152    
153            if (namespace.containsComponentType(type))
154            {
155                setSpecification(namespace.getComponentSpecification(type));
156                return;
157            }
158    
159            IComponentSpecification spec = searchForComponent(cycle);
160    
161            // If not found after search, check to see if it's in
162            // the framework instead.
163    
164            if (spec == null)
165            {
166                throw new ApplicationRuntimeException(ResolverMessages.noSuchComponentType(
167                  type,
168                  namespace), location, null);
169    
170            }
171    
172            setSpecification(spec);
173    
174            // Install it into the namespace, to short-circuit any future search.
175    
176            install();
177        }
178    
179        // Hm. This could maybe go elsewhere, say onto ISpecificationSource
180    
181        private IComponentSpecification searchForComponent(IRequestCycle cycle)
182        {
183            IComponentSpecification result = null;
184            INamespace namespace = getNamespace();
185    
186            if (_log.isDebugEnabled())
187                _log.debug(ResolverMessages.resolvingComponent(_type, namespace));
188    
189            String expectedName = _type + ".jwc";
190            Resource namespaceLocation = namespace.getSpecificationLocation();
191    
192            // Look for appropriate file in same folder as the library (or application)
193            // specificaiton.
194    
195            result = check(namespaceLocation.getRelativeResource(expectedName));
196    
197            if (result != null)
198                return result;
199    
200            if (namespace.isApplicationNamespace()) {
201    
202                // The application namespace gets some extra searching.
203    
204                result = check(getWebInfAppLocation().getRelativeResource(expectedName));
205    
206                if (result == null)
207                    result = check(getWebInfLocation().getRelativeResource(expectedName));
208    
209                if (result == null)
210                    result = check((getContextRoot().getRelativeResource(expectedName)));
211    
212                if (result != null)
213                    return result;
214            }
215    
216            result = getDelegate().findComponentSpecification(cycle, namespace, _type);
217            if (result != null)
218                return result;
219    
220            result = searchForComponentClass(namespace, _type);
221    
222            if (result != null)
223                return result;
224    
225            // Not in the library or app spec; does it match a component
226            // provided by the Framework?
227    
228            INamespace framework = getSpecificationSource().getFrameworkNamespace();
229    
230            if (framework.containsComponentType(_type))
231                return framework.getComponentSpecification(_type);
232    
233            return null;
234        }
235    
236        IComponentSpecification searchForComponentClass(INamespace namespace, String type)
237        {
238            String packages = namespace.getPropertyValue("org.apache.tapestry.component-class-packages");
239    
240            String className = type.replace('/', '.');
241    
242            Class componentClass = _classFinder.findClass(packages, className);
243            if (componentClass == null)
244                return null;
245    
246            IComponentSpecification spec = new ComponentSpecification();
247    
248            Resource namespaceResource = namespace.getSpecificationLocation();
249            Resource componentResource = namespaceResource.getRelativeResource(type + ".jwc");
250    
251            // try classpath relative if namespace relative doesn't resolve
252    
253            if (componentResource.getResourceURL() == null) {
254    
255                componentResource = new ClasspathResource(_classResolver, componentClass.getName().replace('.', '/'));
256            }
257    
258            Location location = new LocationImpl(componentResource);
259    
260            spec.setLocation(location);
261            spec.setSpecificationLocation(componentResource);
262            spec.setComponentClassName(componentClass.getName());
263    
264            return spec;
265        }
266    
267        private IComponentSpecification check(Resource resource)
268        {
269            if (_log.isDebugEnabled())
270                _log.debug("Checking: " + resource);
271    
272            if (resource.getResourceURL() == null)
273                return null;
274    
275            return getSpecificationSource().getComponentSpecification(resource);
276        }
277    
278        private void install()
279        {
280            INamespace namespace = getNamespace();
281            IComponentSpecification specification = getSpecification();
282    
283            if (_log.isDebugEnabled())
284                _log.debug(ResolverMessages.installingComponent(_type, namespace, specification));
285    
286            namespace.installComponentSpecification(_type, specification);
287        }
288    
289        public String getType()
290        {
291            return _type;
292        }
293    
294        public void setLog(Log log)
295        {
296            _log = log;
297        }
298    
299        public void setClassFinder(ClassFinder classFinder)
300        {
301            _classFinder = classFinder;
302        }
303    
304        public void setClassResolver(ClassResolver classResolver)
305        {
306            _classResolver = classResolver;
307        }
308    }