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.Resource;
020    import org.apache.hivemind.impl.LocationImpl;
021    import org.apache.tapestry.INamespace;
022    import org.apache.tapestry.IRequestCycle;
023    import org.apache.tapestry.PageNotFoundException;
024    import org.apache.tapestry.Tapestry;
025    import org.apache.tapestry.services.ComponentPropertySource;
026    import org.apache.tapestry.spec.ComponentSpecification;
027    import org.apache.tapestry.spec.IComponentSpecification;
028    
029    /**
030     * Performs the tricky work of resolving a page name to a page specification. The search for pages
031     * in the application namespace is the most complicated, since Tapestry searches for pages that
032     * aren't explicitly defined in the application specification. The search, based on the
033     * <i>simple-name </i> of the page, goes as follows:
034     * <ul>
035     * <li>As declared in the application specification
036     * <li><i>simple-name </i>.page in the same folder as the application specification
037     * <li><i>simple-name </i> page in the WEB-INF/ <i>servlet-name </i> directory of the context root
038     * <li><i>simple-name </i>.page in WEB-INF
039     * <li><i>simple-name </i>.page in the application root (within the context root)
040     * <li><i>simple-name </i>.html as a template in the application root, for which an implicit
041     * specification is generated
042     * <li>By searching the framework namespace
043     * <li>By invoking
044     * {@link org.apache.tapestry.resolver.ISpecificationResolverDelegate#findPageSpecification(IRequestCycle, INamespace, String)}
045     * </ul>
046     * <p>
047     * Pages in a component library are searched for in a more abbreviated fashion:
048     * <ul>
049     * <li>As declared in the library specification
050     * <li><i>simple-name </i>.page in the same folder as the library specification
051     * <li>By searching the framework namespace
052     * <li>By invoking
053     * {@link org.apache.tapestry.resolver.ISpecificationResolverDelegate#findPageSpecification(IRequestCycle, INamespace, String)}
054     * </ul>
055     * 
056     * @see org.apache.tapestry.engine.IPageSource
057     * @author Howard Lewis Ship
058     * @since 3.0
059     */
060    
061    public class PageSpecificationResolverImpl extends AbstractSpecificationResolver implements
062            PageSpecificationResolver
063    {
064        private static final String WEB_INF = "/WEB-INF/";
065    
066        /** set by container */
067        private Log _log;
068    
069        /** Set by resolve() */
070        private String _simpleName;
071    
072        /** @since 4.0 * */
073        private INamespace _applicationNamespace;
074    
075        /** @since 4.0 * */
076        private INamespace _frameworkNamespace;
077    
078        /** @since 4.0 */
079    
080        private ComponentPropertySource _componentPropertySource;
081    
082        public void initializeService()
083        {
084            _applicationNamespace = getSpecificationSource().getApplicationNamespace();
085            _frameworkNamespace = getSpecificationSource().getFrameworkNamespace();
086    
087            super.initializeService();
088        }
089    
090        protected void reset()
091        {
092            _simpleName = null;
093    
094            super.reset();
095        }
096    
097        /**
098         * Resolve the name (which may have a library id prefix) to a namespace (see
099         * {@link #getNamespace()}) and a specification (see {@link #getSpecification()}).
100         * 
101         * @throws ApplicationRuntimeException
102         *             if the name cannot be resolved
103         */
104    
105        public void resolve(IRequestCycle cycle, String prefixedName)
106        {
107            reset();
108    
109            INamespace namespace = null;
110    
111            int colonx = prefixedName.indexOf(':');
112    
113            if (colonx > 0)
114            {
115                _simpleName = prefixedName.substring(colonx + 1);
116                String namespaceId = prefixedName.substring(0, colonx);
117    
118                namespace = findNamespaceForId(_applicationNamespace, namespaceId);
119            }
120            else
121            {
122                _simpleName = prefixedName;
123    
124                namespace = _applicationNamespace;
125            }
126    
127            setNamespace(namespace);
128    
129            if (namespace.containsPage(_simpleName))
130            {
131                setSpecification(namespace.getPageSpecification(_simpleName));
132                return;
133            }
134    
135            // Not defined in the specification, so it's time to hunt it down.
136    
137            searchForPage(cycle);
138    
139            if (getSpecification() == null)
140                throw new PageNotFoundException(ResolverMessages.noSuchPage(_simpleName, namespace));
141        }
142    
143        public String getSimplePageName()
144        {
145            return _simpleName;
146        }
147    
148        private void searchForPage(IRequestCycle cycle)
149        {
150            INamespace namespace = getNamespace();
151    
152            if (_log.isDebugEnabled())
153                _log.debug(ResolverMessages.resolvingPage(_simpleName, namespace));
154    
155            // Check with and without the leading slash
156    
157            if (_simpleName.regionMatches(true, 0, WEB_INF, 0, WEB_INF.length())
158                    || _simpleName.regionMatches(true, 0, WEB_INF, 1, WEB_INF.length() - 1))
159                throw new ApplicationRuntimeException(ResolverMessages.webInfNotAllowed(_simpleName));
160    
161            String expectedName = _simpleName + ".page";
162    
163            Resource namespaceLocation = namespace.getSpecificationLocation();
164    
165            // See if there's a specification file in the same folder
166            // as the library or application specification that's
167            // supposed to contain the page.
168    
169            if (found(namespaceLocation, expectedName))
170                return;
171    
172            if (namespace.isApplicationNamespace())
173            {
174    
175                // The application namespace gets some extra searching.
176    
177                if (found(getWebInfAppLocation(), expectedName))
178                    return;
179    
180                if (found(getWebInfLocation(), expectedName))
181                    return;
182    
183                if (found(getContextRoot(), expectedName))
184                    return;
185    
186                // The wierd one ... where we see if there's a template in the application root
187                // location.
188    
189                String templateName = _simpleName + "." + getTemplateExtension();
190    
191                Resource templateResource = getContextRoot().getRelativeResource(templateName);
192    
193                if (_log.isDebugEnabled())
194                    _log.debug(ResolverMessages.checkingResource(templateResource));
195    
196                if (templateResource.getResourceURL() != null)
197                {
198                    setupImplicitPage(templateResource, namespaceLocation);
199                    return;
200                }
201    
202                // Not found in application namespace, so maybe its a framework page.
203    
204                if (_frameworkNamespace.containsPage(_simpleName))
205                {
206                    if (_log.isDebugEnabled())
207                        _log.debug(ResolverMessages.foundFrameworkPage(_simpleName));
208    
209                    setNamespace(_frameworkNamespace);
210    
211                    // Note: This implies that normal lookup rules don't work
212                    // for the framework! Framework pages must be
213                    // defined in the framework library specification.
214    
215                    setSpecification(_frameworkNamespace.getPageSpecification(_simpleName));
216                    return;
217                }
218            }
219    
220            // Not found by any normal rule, so its time to
221            // consult the delegate.
222    
223            IComponentSpecification specification = getDelegate().findPageSpecification(
224                    cycle,
225                    namespace,
226                    _simpleName);
227    
228            if (specification != null)
229            {
230                setSpecification(specification);
231                install();
232            }
233        }
234    
235        private void setupImplicitPage(Resource resource, Resource namespaceLocation)
236        {
237            if (_log.isDebugEnabled())
238                _log.debug(ResolverMessages.foundHTMLTemplate(resource));
239    
240            // TODO The SpecFactory in Specification parser should be used in some way to
241            // create an IComponentSpecification!
242    
243            // The virtual location of the page specification is relative to the
244            // namespace (typically, the application specification). This will be used when
245            // searching for the page's message catalog or other related assets.
246    
247            Resource pageResource = namespaceLocation.getRelativeResource(_simpleName + ".page");
248    
249            IComponentSpecification specification = new ComponentSpecification();
250            specification.setPageSpecification(true);
251            specification.setSpecificationLocation(pageResource);
252            specification.setLocation(new LocationImpl(resource));
253    
254            setSpecification(specification);
255    
256            install();
257        }
258    
259        private boolean found(Resource baseResource, String expectedName)
260        {
261            Resource resource = baseResource.getRelativeResource(expectedName);
262    
263            if (_log.isDebugEnabled())
264                _log.debug(ResolverMessages.checkingResource(resource));
265    
266            if (resource.getResourceURL() == null)
267                return false;
268    
269            setSpecification(getSpecificationSource().getPageSpecification(resource));
270    
271            install();
272    
273            return true;
274        }
275    
276        private void install()
277        {
278            INamespace namespace = getNamespace();
279            IComponentSpecification specification = getSpecification();
280    
281            if (_log.isDebugEnabled())
282                _log.debug(ResolverMessages.installingPage(_simpleName, namespace, specification));
283    
284            namespace.installPageSpecification(_simpleName, specification);
285        }
286    
287        /**
288         * If the namespace defines the template extension (as property
289         * {@link Tapestry#TEMPLATE_EXTENSION_PROPERTY}, then that is used, otherwise the default is
290         * used.
291         */
292    
293        private String getTemplateExtension()
294        {
295            return _componentPropertySource.getNamespaceProperty(
296                    getNamespace(),
297                    Tapestry.TEMPLATE_EXTENSION_PROPERTY);
298        }
299    
300        /** @since 4.0 */
301    
302        public void setLog(Log log)
303        {
304            _log = log;
305        }
306    
307        /** @since 4.0 */
308        public void setComponentPropertySource(ComponentPropertySource componentPropertySource)
309        {
310            _componentPropertySource = componentPropertySource;
311        }
312    }