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}