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 }