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