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 }