001: // Copyright 2006, 2007 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.internal.services;
016:
017: import static org.apache.tapestry.ioc.IOCConstants.PERTHREAD_SCOPE;
018: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newCaseInsensitiveMap;
019:
020: import java.net.URL;
021: import java.util.List;
022: import java.util.Map;
023: import java.util.regex.Matcher;
024: import java.util.regex.Pattern;
025:
026: import javax.servlet.http.Cookie;
027:
028: import org.apache.commons.logging.Log;
029: import org.apache.tapestry.Binding;
030: import org.apache.tapestry.ComponentResources;
031: import org.apache.tapestry.internal.bindings.LiteralBinding;
032: import org.apache.tapestry.internal.bindings.PropBindingFactory;
033: import org.apache.tapestry.internal.events.InvalidationListener;
034: import org.apache.tapestry.internal.util.IntegerRange;
035: import org.apache.tapestry.ioc.Location;
036: import org.apache.tapestry.ioc.MappedConfiguration;
037: import org.apache.tapestry.ioc.ObjectProvider;
038: import org.apache.tapestry.ioc.OrderedConfiguration;
039: import org.apache.tapestry.ioc.ServiceBinder;
040: import org.apache.tapestry.ioc.ServiceResources;
041: import org.apache.tapestry.ioc.annotations.InjectService;
042: import org.apache.tapestry.ioc.annotations.Scope;
043: import org.apache.tapestry.ioc.annotations.Symbol;
044: import org.apache.tapestry.ioc.services.ChainBuilder;
045: import org.apache.tapestry.ioc.services.ClassFactory;
046: import org.apache.tapestry.ioc.services.PropertyAccess;
047: import org.apache.tapestry.ioc.services.ThreadCleanupHub;
048: import org.apache.tapestry.ioc.services.ThreadLocale;
049: import org.apache.tapestry.ioc.services.TypeCoercer;
050: import org.apache.tapestry.services.ActionResponseGenerator;
051: import org.apache.tapestry.services.ApplicationGlobals;
052: import org.apache.tapestry.services.ApplicationInitializer;
053: import org.apache.tapestry.services.ApplicationInitializerFilter;
054: import org.apache.tapestry.services.AssetFactory;
055: import org.apache.tapestry.services.BindingFactory;
056: import org.apache.tapestry.services.ClasspathAssetAliasManager;
057: import org.apache.tapestry.services.ComponentActionRequestFilter;
058: import org.apache.tapestry.services.ComponentActionRequestHandler;
059: import org.apache.tapestry.services.ComponentClassResolver;
060: import org.apache.tapestry.services.ComponentMessagesSource;
061: import org.apache.tapestry.services.Context;
062: import org.apache.tapestry.services.ObjectRenderer;
063: import org.apache.tapestry.services.PersistentFieldStrategy;
064: import org.apache.tapestry.services.PropertyConduitSource;
065: import org.apache.tapestry.services.Request;
066: import org.apache.tapestry.services.RequestExceptionHandler;
067: import org.apache.tapestry.services.RequestFilter;
068: import org.apache.tapestry.services.RequestGlobals;
069: import org.apache.tapestry.services.ResourceDigestGenerator;
070:
071: public final class InternalModule {
072: public static void bind(ServiceBinder binder) {
073: binder.bind(TemplateParser.class, TemplateParserImpl.class);
074: binder.bind(PageResponseRenderer.class,
075: PageResponseRendererImpl.class);
076: binder.bind(PageMarkupRenderer.class,
077: PageMarkupRendererImpl.class);
078: binder.bind(ComponentInvocationMap.class,
079: NoOpComponentInvocationMap.class);
080: binder.bind(ObjectRenderer.class, LocationRenderer.class)
081: .withId("LocationRenderer");
082: binder.bind(UpdateListenerHub.class,
083: UpdateListenerHubImpl.class);
084: binder.bind(ObjectProvider.class, AssetObjectProvider.class)
085: .withId("AssetObjectProvider");
086: binder.bind(LinkFactory.class, LinkFactoryImpl.class);
087: binder.bind(LocalizationSetter.class,
088: LocalizationSetterImpl.class);
089: binder.bind(PageElementFactory.class,
090: PageElementFactoryImpl.class);
091: binder.bind(ClassNameLocator.class, ClassNameLocatorImpl.class);
092: binder.bind(RequestExceptionHandler.class,
093: DefaultRequestExceptionHandler.class);
094: binder.bind(ResourceStreamer.class, ResourceStreamerImpl.class);
095: binder.bind(ClientPersistentFieldStorage.class,
096: ClientPersistentFieldStorageImpl.class);
097: binder.bind(RequestEncodingInitializer.class,
098: RequestEncodingInitializerImpl.class);
099: }
100:
101: public static void contributeTemplateParser(
102: MappedConfiguration<String, URL> configuration) {
103: Class c = InternalModule.class;
104: configuration.add("-//W3C//DTD XHTML 1.0 Strict//EN", c
105: .getResource("xhtml1-strict.dtd"));
106: configuration.add("-//W3C//DTD XHTML 1.0 Transitional//EN", c
107: .getResource("xhtml1-transitional.dtd"));
108: configuration.add("-//W3C//DTD XHTML 1.0 Frameset//EN", c
109: .getResource("xhtml1-frameset.dtd"));
110: configuration.add("-//W3C//ENTITIES Latin 1 for XHTML//EN", c
111: .getResource("xhtml-lat1.ent"));
112: configuration.add("-//W3C//ENTITIES Symbols for XHTML//EN", c
113: .getResource("xhtml-symbol.ent"));
114: configuration.add("-//W3C//ENTITIES Special for XHTML//EN", c
115: .getResource("xhtml-special.ent"));
116: }
117:
118: /**
119: * Contributes factory defaults that map be overridden.
120: */
121: public static void contributeFactoryDefaults(
122: MappedConfiguration<String, String> configuration) {
123: // Remember this is request-to-request time, presumably it'll take the developer more than
124: // one second to make a change, save it, and switch back to the browser.
125:
126: configuration.add("tapestry.file-check-interval", "1000"); // 1 second
127: configuration.add("tapestry.file-check-update-timeout", "50"); // 50 milliseconds
128: configuration.add("tapestry.supported-locales", "en");
129: configuration.add("tapestry.default-cookie-max-age", "604800"); // One week
130:
131: configuration.add("tapestry.start-page-name", "start");
132:
133: // This is designed to make it easy to keep syncrhonized with script.aculo.ous. As we
134: // support a new version, we create a new folder, and update the path entry. We can then
135: // delete the old version folder (or keep it around). This should be more manageable than
136: // ovewriting the local copy with updates. There's also a ClasspathAliasManager
137: // contribution based on the path.
138:
139: configuration.add("tapestry.scriptaculous",
140: "classpath:${tapestry.scriptaculous.path}");
141: configuration.add("tapestry.scriptaculous.path",
142: "org/apache/tapestry/scriptaculous_1_7_0");
143: }
144:
145: private final ComponentInstantiatorSource _componentInstantiatorSource;
146:
147: private final ComponentTemplateSource _componentTemplateSource;
148:
149: private final UpdateListenerHub _updateListenerHub;
150:
151: private final ThreadCleanupHub _threadCleanupHub;
152:
153: private final ChainBuilder _chainBuilder;
154:
155: private final Request _request;
156:
157: private final ThreadLocale _threadLocale;
158:
159: private final RequestGlobals _requestGlobals;
160:
161: public InternalModule(
162: ComponentInstantiatorSource componentInstantiatorSource,
163: UpdateListenerHub updateListenerHub,
164: ThreadCleanupHub threadCleanupHub,
165: ComponentTemplateSource componentTemplateSource,
166: ChainBuilder chainBuilder, Request request,
167: ThreadLocale threadLocale, RequestGlobals requestGlobals) {
168: _componentInstantiatorSource = componentInstantiatorSource;
169: _updateListenerHub = updateListenerHub;
170: _threadCleanupHub = threadCleanupHub;
171: _componentTemplateSource = componentTemplateSource;
172: _chainBuilder = chainBuilder;
173: _request = request;
174: _threadLocale = threadLocale;
175: _requestGlobals = requestGlobals;
176: }
177:
178: public PageTemplateLocator build(
179: @InjectService("ContextAssetFactory")
180: AssetFactory contextAssetFactory,
181:
182: ComponentClassResolver componentClassResolver) {
183: return new PageTemplateLocatorImpl(contextAssetFactory
184: .getRootResource(), componentClassResolver);
185: }
186:
187: public ComponentInstantiatorSource build(
188: @InjectService("ClassFactory")
189: ClassFactory classFactory,
190:
191: ComponentClassTransformer transformer,
192:
193: Log log) {
194: ComponentInstantiatorSourceImpl source = new ComponentInstantiatorSourceImpl(
195: classFactory.getClassLoader(), transformer, log);
196:
197: _updateListenerHub.addUpdateListener(source);
198:
199: return source;
200: }
201:
202: public ComponentClassTransformer buildComponentClassTransformer(
203: ServiceResources resources) {
204: ComponentClassTransformerImpl transformer = resources
205: .autobuild(ComponentClassTransformerImpl.class);
206:
207: _componentInstantiatorSource
208: .addInvalidationListener(transformer);
209:
210: return transformer;
211: }
212:
213: public PagePool build(Log log, PageLoader pageLoader,
214: ComponentMessagesSource componentMessagesSource,
215: ComponentClassResolver resolver) {
216: PagePoolImpl service = new PagePoolImpl(log, pageLoader,
217: _threadLocale, resolver);
218:
219: // This covers invalidations due to changes to classes
220:
221: pageLoader.addInvalidationListener(service);
222:
223: // This covers invalidation due to changes to message catalogs (properties files)
224:
225: componentMessagesSource.addInvalidationListener(service);
226:
227: // ... and this covers invalidations due to changes to templates
228:
229: _componentTemplateSource.addInvalidationListener(service);
230:
231: return service;
232: }
233:
234: public PageLoader buildPageLoader(ServiceResources resources) {
235: PageLoaderImpl service = resources
236: .autobuild(PageLoaderImpl.class);
237:
238: // Recieve invalidations when the class loader is discarded (due to a component class
239: // change). The notification is forwarded to the page loader's listeners.
240:
241: _componentInstantiatorSource.addInvalidationListener(service);
242:
243: return service;
244: }
245:
246: @Scope(PERTHREAD_SCOPE)
247: public RequestPageCache build(PagePool pagePool) {
248: RequestPageCacheImpl service = new RequestPageCacheImpl(
249: pagePool);
250:
251: _threadCleanupHub.addThreadCleanupListener(service);
252:
253: return service;
254: }
255:
256: public ResourceCache build(ResourceDigestGenerator digestGenerator) {
257: ResourceCacheImpl service = new ResourceCacheImpl(
258: digestGenerator);
259:
260: _updateListenerHub.addUpdateListener(service);
261:
262: return service;
263: }
264:
265: public ComponentTemplateSource build(TemplateParser parser,
266: PageTemplateLocator locator) {
267: ComponentTemplateSourceImpl service = new ComponentTemplateSourceImpl(
268: parser, locator);
269:
270: _updateListenerHub.addUpdateListener(service);
271:
272: return service;
273: }
274:
275: public AssetFactory buildClasspathAssetFactory(
276: ResourceCache resourceCache,
277:
278: ClasspathAssetAliasManager aliasManager) {
279: ClasspathAssetFactory factory = new ClasspathAssetFactory(
280: resourceCache, aliasManager);
281:
282: resourceCache.addInvalidationListener(factory);
283:
284: return factory;
285: }
286:
287: public AssetFactory buildContextAssetFactory(
288: ApplicationGlobals globals) {
289: return new ContextAssetFactory(_request, globals.getContext());
290: }
291:
292: public CookieSink buildCookieSink() {
293: return new CookieSink() {
294:
295: public void addCookie(Cookie cookie) {
296: _requestGlobals.getHTTPServletResponse().addCookie(
297: cookie);
298: }
299:
300: };
301: }
302:
303: public CookieSource buildCookieSource() {
304: return new CookieSource() {
305:
306: public Cookie[] getCookies() {
307: return _requestGlobals.getHTTPServletRequest()
308: .getCookies();
309: }
310:
311: };
312: }
313:
314: /**
315: * Builds the PropBindingFactory as a chain of command. The terminator of the chain is
316: * responsible for ordinary property names (and property paths). Contributions to the service
317: * cover additional special cases, such as simple literal values.
318: *
319: * @param configuration
320: * contributions of special factories for some constants, each contributed factory
321: * may return a binding if applicable, or null otherwise
322: */
323: public BindingFactory buildPropBindingFactory(
324: List<BindingFactory> configuration,
325: PropertyConduitSource propertyConduitSource) {
326: PropBindingFactory service = new PropBindingFactory(
327: propertyConduitSource);
328:
329: configuration.add(service);
330:
331: return _chainBuilder.build(BindingFactory.class, configuration);
332: }
333:
334: /**
335: * Adds content types for "css" and "js" file extensions.
336: */
337: public void contributeResourceStreamer(
338: MappedConfiguration<String, String> configuration) {
339: configuration.add("css", "text/css");
340: configuration.add("js", "text/javascript");
341: }
342:
343: /**
344: * Adds a filter that sets the application package (for class loading purposes). The filter is
345: * ordered before:*.*".
346: */
347: public void contributeApplicationInitializer(
348: OrderedConfiguration<ApplicationInitializerFilter> configuration,
349: final ApplicationGlobals applicationGlobals,
350: final PropertyAccess propertyAccess,
351: final TypeCoercer typeCoercer) {
352: final InvalidationListener listener = new InvalidationListener() {
353: public void objectWasInvalidated() {
354: propertyAccess.clearCache();
355: typeCoercer.clearCache();
356: }
357: };
358:
359: ApplicationInitializerFilter clearCaches = new ApplicationInitializerFilter() {
360: public void initializeApplication(Context context,
361: ApplicationInitializer initializer) {
362: // Snuck in here is the logic to clear the PropertyAccess service's cache whenever
363: // the component class loader is invalidated.
364:
365: _componentInstantiatorSource
366: .addInvalidationListener(listener);
367:
368: initializer.initializeApplication(context);
369: }
370: };
371:
372: configuration.add("ClearCachesOnInvalidation", clearCaches);
373: }
374:
375: public void contributePropBindingFactory(
376: OrderedConfiguration<BindingFactory> configuration) {
377: BindingFactory keywordFactory = new BindingFactory() {
378: private final Map<String, Object> _keywords = newCaseInsensitiveMap();
379:
380: {
381: _keywords.put("true", Boolean.TRUE);
382: _keywords.put("false", Boolean.FALSE);
383: _keywords.put("null", null);
384: }
385:
386: public Binding newBinding(String description,
387: ComponentResources container,
388: ComponentResources component, String expression,
389: Location location) {
390: String key = expression.trim();
391:
392: if (_keywords.containsKey(key))
393: return new LiteralBinding(description, _keywords
394: .get(key), location);
395:
396: return null;
397: }
398: };
399:
400: BindingFactory this Factory = new BindingFactory() {
401:
402: public Binding newBinding(String description,
403: ComponentResources container,
404: ComponentResources component, String expression,
405: Location location) {
406: if ("this".equalsIgnoreCase(expression.trim()))
407: return new LiteralBinding(description, container
408: .getComponent(), location);
409:
410: return null;
411: }
412: };
413:
414: BindingFactory longFactory = new BindingFactory() {
415: private final Pattern _pattern = Pattern
416: .compile("^\\s*(-?\\d+)\\s*$");
417:
418: public Binding newBinding(String description,
419: ComponentResources container,
420: ComponentResources component, String expression,
421: Location location) {
422: Matcher matcher = _pattern.matcher(expression);
423:
424: if (matcher.matches()) {
425: String value = matcher.group(1);
426:
427: return new LiteralBinding(description, new Long(
428: value), location);
429: }
430:
431: return null;
432: }
433: };
434:
435: BindingFactory intRangeFactory = new BindingFactory() {
436: private final Pattern _pattern = Pattern
437: .compile("^\\s*(-?\\d+)\\s*\\.\\.\\s*(-?\\d+)\\s*$");
438:
439: public Binding newBinding(String description,
440: ComponentResources container,
441: ComponentResources component, String expression,
442: Location location) {
443: Matcher matcher = _pattern.matcher(expression);
444:
445: if (matcher.matches()) {
446: int start = Integer.parseInt(matcher.group(1));
447: int finish = Integer.parseInt(matcher.group(2));
448:
449: IntegerRange range = new IntegerRange(start, finish);
450:
451: return new LiteralBinding(description, range,
452: location);
453: }
454:
455: return null;
456: }
457: };
458:
459: BindingFactory doubleFactory = new BindingFactory() {
460: // So, either 1234. or 1234.56 or .78
461: private final Pattern _pattern = Pattern
462: .compile("^\\s*(\\-?((\\d+\\.)|(\\d*\\.\\d+)))\\s*$");
463:
464: public Binding newBinding(String description,
465: ComponentResources container,
466: ComponentResources component, String expression,
467: Location location) {
468: Matcher matcher = _pattern.matcher(expression);
469:
470: if (matcher.matches()) {
471: String value = matcher.group(1);
472:
473: return new LiteralBinding(description, new Double(
474: value), location);
475: }
476:
477: return null;
478: }
479: };
480:
481: BindingFactory stringFactory = new BindingFactory() {
482: // This will match embedded single quotes as-is, no escaping necessary.
483:
484: private final Pattern _pattern = Pattern
485: .compile("^\\s*'(.*)'\\s*$");
486:
487: public Binding newBinding(String description,
488: ComponentResources container,
489: ComponentResources component, String expression,
490: Location location) {
491: Matcher matcher = _pattern.matcher(expression);
492:
493: if (matcher.matches()) {
494: String value = matcher.group(1);
495:
496: return new LiteralBinding(description, value,
497: location);
498: }
499:
500: return null;
501: }
502: };
503:
504: // To be honest, order probably doesn't matter.
505:
506: configuration.add("Keyword", keywordFactory);
507: configuration.add("This", this Factory);
508: configuration.add("Long", longFactory);
509: configuration.add("IntRange", intRangeFactory);
510: configuration.add("Double", doubleFactory);
511: configuration.add("StringLiteral", stringFactory);
512: }
513:
514: /**
515: * Adds a filter that checks for updates to classes and other resources. It is ordered before:*.
516: */
517: public void contributeRequestHandler(
518: OrderedConfiguration<RequestFilter> configuration,
519: RequestGlobals requestGlobals,
520:
521: // @Inject not needed because its a long, not a String
522: @Symbol("tapestry.file-check-interval")
523: long checkInterval,
524:
525: @Symbol("tapestry.file-check-update-timeout")
526: long updateTimeout,
527:
528: LocalizationSetter localizationSetter) {
529: configuration.add("CheckForUpdates", new CheckForUpdatesFilter(
530: _updateListenerHub, checkInterval, updateTimeout),
531: "before:*");
532:
533: configuration.add("Localization", new LocalizationFilter(
534: localizationSetter));
535: }
536:
537: public PersistentFieldStrategy buildClientPersistentFieldStrategy(
538: LinkFactory linkFactory, ServiceResources resources) {
539: ClientPersistentFieldStrategy service = resources
540: .autobuild(ClientPersistentFieldStrategy.class);
541:
542: linkFactory.addListener(service);
543:
544: return service;
545: }
546:
547: public static void contributeComponentActionRequestHandler(
548: OrderedConfiguration<ComponentActionRequestFilter> configuration,
549: final RequestEncodingInitializer encodingInitializer) {
550: ComponentActionRequestFilter filter = new ComponentActionRequestFilter() {
551: public ActionResponseGenerator handle(
552: String logicalPageName, String nestedComponentId,
553: String eventType, String[] context,
554: String[] activationContext,
555: ComponentActionRequestHandler handler) {
556: encodingInitializer
557: .initializeRequestEncoding(logicalPageName);
558:
559: return handler.handle(logicalPageName,
560: nestedComponentId, eventType, context,
561: activationContext);
562: }
563:
564: };
565:
566: configuration.add("SetRequestEncoding", filter, "before:*");
567: }
568: }
|