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