001// Copyright 2010, 2011, 2012 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.tapestry5.internal.services;
016
017import org.apache.tapestry5.ioc.*;
018import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
019import org.apache.tapestry5.ioc.internal.util.LockSupport;
020import org.apache.tapestry5.ioc.services.PlasticProxyFactory;
021import org.apache.tapestry5.ioc.services.ThreadLocale;
022import org.apache.tapestry5.services.InvalidationListener;
023import org.apache.tapestry5.services.messages.ComponentMessagesSource;
024
025import java.util.Locale;
026import java.util.Map;
027
028/**
029 * Allows for injection of the global application message catalog into services. The injected value
030 * is, in fact, a proxy. Each method access of the proxy will determine the current thread's locale, and delegate
031 * to the actual global message catalog for that particular locale. There's caching to keep it reasonably
032 * efficient.
033 *
034 * @see ComponentMessagesSource#getApplicationCatalog(Locale)
035 * @since 5.2.0
036 */
037public class ApplicationMessageCatalogObjectProvider extends LockSupport implements ObjectProvider
038{
039    private final ObjectLocator objectLocator;
040
041    private ComponentMessagesSource messagesSource;
042
043    private ThreadLocale threadLocale;
044
045    private final Map<Locale, Messages> localeToMessages = CollectionFactory.newConcurrentMap();
046
047    private Messages proxy;
048
049    private class ApplicationMessagesObjectCreator implements ObjectCreator<Messages>
050    {
051        public Messages createObject()
052        {
053            Locale locale = threadLocale.getLocale();
054
055            Messages messages = localeToMessages.get(locale);
056
057            if (messages == null)
058            {
059                messages = messagesSource.getApplicationCatalog(locale);
060                localeToMessages.put(locale, messages);
061            }
062
063            return messages;
064        }
065    }
066
067    ;
068
069    public ApplicationMessageCatalogObjectProvider(ObjectLocator locator)
070    {
071        this.objectLocator = locator;
072    }
073
074    /**
075     * Because this is an ObjectProvider and is part of the MasterObjectProvider pipeline, it has to
076     * be careful to not require further dependencies at construction time. This means we have to "drop out"
077     * of normal IoC dependency injection and adopt a lookup strategy based on the ObjectLocator. Further,
078     * we have to be careful about multi-threading issues.
079     */
080    private Messages getProxy()
081    {
082        try
083        {
084            acquireReadLock();
085
086            if (proxy == null)
087            {
088                createProxy();
089            }
090
091            return proxy;
092        } finally
093        {
094            releaseReadLock();
095        }
096    }
097
098    private void createProxy()
099    {
100        try
101        {
102            upgradeReadLockToWriteLock();
103
104            this.messagesSource = objectLocator.getService(ComponentMessagesSource.class);
105            this.threadLocale = objectLocator.getService(ThreadLocale.class);
106
107            PlasticProxyFactory proxyFactory = objectLocator.getService("PlasticProxyFactory",
108                    PlasticProxyFactory.class);
109
110            proxy = proxyFactory.createProxy(Messages.class, new ApplicationMessagesObjectCreator(),
111                    "<ApplicationMessagesProxy>");
112
113            // Listen for invalidations; clear our cache of localized Messages bundles when
114            // an invalidation occurs.
115
116            messagesSource.getInvalidationEventHub().clearOnInvalidation(localeToMessages);
117        } finally
118        {
119            downgradeWriteLockToReadLock();
120        }
121    }
122
123    public <T> T provide(Class<T> objectType, AnnotationProvider annotationProvider, ObjectLocator locator)
124    {
125        if (objectType.equals(Messages.class))
126            return objectType.cast(getProxy());
127
128        return null;
129    }
130
131    public void objectWasInvalidated()
132    {
133        localeToMessages.clear();
134    }
135
136}