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