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 }