001    // Copyright 2007, 2008, 2010 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.tapestry5.internal.services;
016    
017    import org.apache.tapestry5.ComponentResources;
018    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
019    import org.apache.tapestry5.ioc.services.SymbolSource;
020    import org.apache.tapestry5.ioc.services.TypeCoercer;
021    import org.apache.tapestry5.services.InvalidationListener;
022    import org.apache.tapestry5.services.MetaDataLocator;
023    
024    import java.util.Map;
025    
026    public class MetaDataLocatorImpl implements MetaDataLocator, InvalidationListener
027    {
028        private final SymbolSource symbolSource;
029    
030        private final TypeCoercer typeCoercer;
031    
032        private final ComponentModelSource modelSource;
033    
034        private final Map<String, Map<String, String>> defaultsByFolder = CollectionFactory
035                .newCaseInsensitiveMap();
036    
037        private final Map<String, String> cache = CollectionFactory.newConcurrentMap();
038    
039        private interface ValueLocator
040        {
041            String valueForKey(String key);
042        }
043    
044        public MetaDataLocatorImpl(SymbolSource symbolSource, TypeCoercer typeCoercer,
045                ComponentModelSource modelSource, Map<String, String> configuration)
046        {
047            this.symbolSource = symbolSource;
048            this.typeCoercer = typeCoercer;
049            this.modelSource = modelSource;
050    
051            loadDefaults(configuration);
052        }
053    
054        public void objectWasInvalidated()
055        {
056            cache.clear();
057        }
058    
059        private void loadDefaults(Map<String, String> configuration)
060        {
061            for (Map.Entry<String, String> e : configuration.entrySet())
062            {
063                String key = e.getKey();
064    
065                int colonx = key.indexOf(':');
066    
067                String folderKey = colonx < 0 ? "" : key.substring(0, colonx);
068    
069                Map<String, String> forFolder = defaultsByFolder.get(folderKey);
070    
071                if (forFolder == null)
072                {
073                    forFolder = CollectionFactory.newCaseInsensitiveMap();
074                    defaultsByFolder.put(folderKey, forFolder);
075                }
076    
077                String defaultKey = colonx < 0 ? key : key.substring(colonx + 1);
078    
079                forFolder.put(defaultKey, e.getValue());
080            }
081        }
082    
083        public <T> T findMeta(String key, final ComponentResources resources, Class<T> expectedType)
084        {
085            String value = getSymbolExpandedValueFromCache(key, resources.getCompleteId() + "/" + key,
086                    new ValueLocator()
087                    {
088                        public String valueForKey(String key)
089                        {
090                            return locate(key, resources);
091                        }
092                    });
093    
094            return typeCoercer.coerce(value, expectedType);
095        }
096    
097        public <T> T findMeta(String key, final String pageName, Class<T> expectedType)
098        {
099            String value = getSymbolExpandedValueFromCache(key, pageName + "/" + key,
100                    new ValueLocator()
101                    {
102                        public String valueForKey(String key)
103                        {
104                            String result = modelSource.getPageModel(pageName).getMeta(key);
105    
106                            return result != null ? result : locateInDefaults(key, pageName);
107                        }
108                    });
109    
110            return typeCoercer.coerce(value, expectedType);
111        }
112    
113        private String getSymbolExpandedValueFromCache(String key, String cacheKey,
114                ValueLocator valueLocator)
115        {
116            if (cache.containsKey(cacheKey))
117                return cache.get(cacheKey);
118    
119            String value = valueLocator.valueForKey(key);
120    
121            if (value == null)
122            {
123                value = symbolSource.valueForSymbol(key);
124            }
125            else
126            {
127                value = symbolSource.expandSymbols(value);
128            }
129    
130            cache.put(cacheKey, value);
131    
132            return value;
133        }
134    
135        private String locate(String key, ComponentResources resources)
136        {
137            ComponentResources cursor = resources;
138    
139            while (true)
140            {
141                String value = cursor.getComponentModel().getMeta(key);
142    
143                if (value != null)
144                    return value;
145    
146                ComponentResources next = cursor.getContainerResources();
147    
148                if (next == null)
149                    return locateInDefaults(key, cursor.getPageName());
150    
151                cursor = next;
152            }
153        }
154    
155        private String locateInDefaults(String key, String pageName)
156        {
157    
158            // We're going to peel this apart, slash by slash. Thus for
159            // "mylib/myfolder/mysubfolder/MyPage" we'll be checking: "mylib/myfolder/mysubfolder",
160            // then "mylib/myfolder", then "mylib", then "".
161    
162            String path = pageName;
163    
164            while (true)
165            {
166                int lastSlashx = path.lastIndexOf('/');
167    
168                String folderKey = lastSlashx < 0 ? "" : path.substring(0, lastSlashx);
169    
170                Map<String, String> forFolder = defaultsByFolder.get(folderKey);
171    
172                if (forFolder != null && forFolder.containsKey(key))
173                    return forFolder.get(key);
174    
175                if (lastSlashx < 0)
176                    break;
177    
178                path = path.substring(0, lastSlashx);
179            }
180    
181            // Perhaps from here into the symbol sources? That may come later.
182    
183            return null;
184        }
185    }