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
015package org.apache.tapestry.resolver;
016
017import org.apache.commons.logging.Log;
018import org.apache.hivemind.ApplicationRuntimeException;
019import org.apache.hivemind.Resource;
020import org.apache.hivemind.impl.LocationImpl;
021import org.apache.tapestry.INamespace;
022import org.apache.tapestry.IRequestCycle;
023import org.apache.tapestry.PageNotFoundException;
024import org.apache.tapestry.Tapestry;
025import org.apache.tapestry.services.ComponentPropertySource;
026import org.apache.tapestry.spec.ComponentSpecification;
027import 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
061public 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}