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 }