001// Copyright 2006-2013 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 java.util.Arrays;
018import java.util.List;
019import java.util.Locale;
020import java.util.concurrent.Callable;
021
022import org.apache.tapestry5.commons.Messages;
023import org.apache.tapestry5.commons.Resource;
024import org.apache.tapestry5.commons.internal.util.TapestryException;
025import org.apache.tapestry5.commons.services.InvalidationEventHub;
026import org.apache.tapestry5.http.TapestryHttpSymbolConstants;
027import org.apache.tapestry5.ioc.annotations.PostInjection;
028import org.apache.tapestry5.ioc.annotations.Symbol;
029import org.apache.tapestry5.ioc.internal.util.URLChangeTracker;
030import org.apache.tapestry5.ioc.services.ClasspathURLConverter;
031import org.apache.tapestry5.ioc.services.ThreadLocale;
032import org.apache.tapestry5.ioc.services.UpdateListener;
033import org.apache.tapestry5.model.ComponentModel;
034import org.apache.tapestry5.services.messages.ComponentMessagesSource;
035import org.apache.tapestry5.services.messages.PropertiesFileParser;
036import org.apache.tapestry5.services.pageload.ComponentRequestSelectorAnalyzer;
037import org.apache.tapestry5.services.pageload.ComponentResourceLocator;
038import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
039
040public class ComponentMessagesSourceImpl implements ComponentMessagesSource, UpdateListener
041{
042    private final MessagesSourceImpl messagesSource;
043
044    private final MessagesBundle appCatalogBundle;
045    
046    private final ComponentRequestSelectorAnalyzer componentRequestSelectorAnalyzer;
047    
048    private final ThreadLocale threadLocale;
049
050    private class ComponentModelBundle implements MessagesBundle
051    {
052        private final ComponentModel model;
053
054        public ComponentModelBundle(ComponentModel model)
055        {
056            this.model = model;
057        }
058
059        public Resource getBaseResource()
060        {
061            return model.getBaseResource();
062        }
063
064        public Object getId()
065        {
066            return model.getComponentClassName();
067        }
068
069        public MessagesBundle getParent()
070        {
071            ComponentModel parentModel = model.getParentModel();
072
073            if (parentModel == null)
074                return appCatalogBundle;
075
076            return new ComponentModelBundle(parentModel);
077        }
078    }
079
080    public ComponentMessagesSourceImpl(@Symbol(TapestryHttpSymbolConstants.PRODUCTION_MODE)
081                                       boolean productionMode, List<Resource> appCatalogResources, PropertiesFileParser parser,
082                                       ComponentResourceLocator resourceLocator, ClasspathURLConverter classpathURLConverter,
083                                       ComponentRequestSelectorAnalyzer componentRequestSelectorAnalyzer,
084                                       ThreadLocale threadLocale)
085    {
086        this(productionMode, appCatalogResources, resourceLocator, parser, new URLChangeTracker(classpathURLConverter), componentRequestSelectorAnalyzer, threadLocale);
087    }
088
089    ComponentMessagesSourceImpl(boolean productionMode, Resource appCatalogResource,
090                                ComponentResourceLocator resourceLocator, PropertiesFileParser parser, 
091                                URLChangeTracker tracker, ComponentRequestSelectorAnalyzer componentRequestSelectorAnalyzer,
092                                ThreadLocale threadLocale)
093    {
094        this(productionMode, Arrays.asList(appCatalogResource), resourceLocator, parser, tracker, componentRequestSelectorAnalyzer, threadLocale);
095    }
096
097    ComponentMessagesSourceImpl(boolean productionMode, List<Resource> appCatalogResources,
098                                ComponentResourceLocator resourceLocator, PropertiesFileParser parser, 
099                                URLChangeTracker tracker, ComponentRequestSelectorAnalyzer componentRequestSelectorAnalyzer,
100                                ThreadLocale threadLocale)
101    {
102        messagesSource = new MessagesSourceImpl(productionMode, productionMode ? null : tracker, resourceLocator,
103                parser);
104
105        appCatalogBundle = createAppCatalogBundle(appCatalogResources);
106        this.componentRequestSelectorAnalyzer = componentRequestSelectorAnalyzer;
107        this.threadLocale = threadLocale;
108    }
109
110    @PostInjection
111    public void setupReload(ReloadHelper reloadHelper)
112    {
113        reloadHelper.addReloadCallback(new Runnable()
114        {
115            public void run()
116            {
117                messagesSource.invalidate();
118            }
119        });
120    }
121
122    public void checkForUpdates()
123    {
124        messagesSource.checkForUpdates();
125    }
126
127    public Messages getMessages(ComponentModel componentModel, Locale locale)
128    {
129       return getMessagesWithForcedLocale(() -> getMessages(componentModel, componentRequestSelectorAnalyzer.buildSelectorForRequest()), locale);
130    }
131    
132    private Messages getMessagesWithForcedLocale(Callable<Messages> callable, Locale locale)
133    {
134        final Locale original = threadLocale.getLocale();
135        try 
136        {
137           threadLocale.setLocale(locale);
138           return callable.call();
139        } catch (Exception e) {
140            throw new TapestryException(e.getMessage(), e);
141        }
142        finally {
143            threadLocale.setLocale(original);
144        }
145    }
146
147    public Messages getMessages(ComponentModel componentModel, ComponentResourceSelector selector)
148    {
149        final MessagesBundle bundle = new ComponentModelBundle(componentModel);
150        return messagesSource.getMessages(bundle, selector);
151    }
152    
153    public Messages getApplicationCatalog(Locale locale)
154    {
155        return getMessagesWithForcedLocale(() -> messagesSource.getMessages(appCatalogBundle, componentRequestSelectorAnalyzer.buildSelectorForRequest()), locale);
156    }
157
158    private MessagesBundle createAppCatalogBundle(List<Resource> resources)
159    {
160        MessagesBundle current = null;
161
162        for (Resource r : resources)
163        {
164            current = createMessagesBundle(r, current);
165        }
166
167        return current;
168    }
169
170    private MessagesBundle createMessagesBundle(final Resource resource, final MessagesBundle parent)
171    {
172        return new MessagesBundle()
173        {
174            public Resource getBaseResource()
175            {
176                return resource;
177            }
178
179            public Object getId()
180            {
181                return resource.getPath();
182            }
183
184            public MessagesBundle getParent()
185            {
186                return parent;
187            }
188        };
189    }
190
191    public InvalidationEventHub getInvalidationEventHub()
192    {
193        return messagesSource;
194    }
195}