`AddonHelper.groovy`
`001 /* 002 * Copyright 2009-2012 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017 package org.codehaus.griffon.runtime.util 018 019 import griffon.util.GriffonNameUtils 020 import groovy.transform.Synchronized 021 import org.codehaus.griffon.runtime.builder.CompositeBuilderHelper 022 import org.codehaus.griffon.runtime.builder.UberBuilder 023 import org.codehaus.griffon.runtime.core.DefaultGriffonAddon 024 import org.codehaus.griffon.runtime.core.DefaultGriffonAddonDescriptor 025 import org.slf4j.Logger 026 import org.slf4j.LoggerFactory 027 import griffon.core.* 028 import static griffon.util.GriffonNameUtils.getClassNameForLowerCaseHyphenSeparatedName 029 030 /** 031 * Helper class for dealing with addon initialization. 032 * 033 * @author Danno Ferrin 034 * @author Andres Almiray 035 */ 036 class AddonHelper { 037 private static final Logger LOG = LoggerFactory.getLogger(AddonHelper) 038 039 private static final Map<String, Map<String, Object>> ADDON_CACHE = [:] 040 041 static final DELEGATE_TYPES = Collections.unmodifiableList([ 042 "attributeDelegates", 043 "preInstantiateDelegates", 044 "postInstantiateDelegates", 045 "postNodeCompletionDelegates" 046 ]) 047 048 @Synchronized 049 private static Map<String, Map<String, Object>> getAddonCache() { 050 ADDON_CACHE 051 } 052 053 @Synchronized 054 private static void computeAddonCache(GriffonApplication app) { 055 if (!ADDON_CACHE.isEmpty()) return 056 057 // Load addons in order 058 URL addonMetadata = AddonHelper.classLoader.getResource('META-INF/griffon-addons.properties') 059 if (addonMetadata) { 060 addonMetadata.text.eachLine { line -> 061 String[] parts = line.split('=') 062 String pluginName = parts[0].trim() 063 ADDON_CACHE[pluginName] = [ 064 node: null, 065 version: parts[1].trim(), 066 prefix: '', 067 name: pluginName, 068 className: getClassNameForLowerCaseHyphenSeparatedName(pluginName) + 'GriffonAddon' 069 ] 070 } 071 } 072 073 for (node in app.builderConfig) { 074 String nodeName = node.key 075 switch (nodeName) { 076 case 'addons': 077 case 'features': 078 // reserved words, not addon prefixes 079 break 080 default: 081 if (nodeName == 'root') nodeName = '' 082 node.value.each { addon -> 083 String pluginName = GriffonNameUtils.getHyphenatedName(addon.key - 'GriffonAddon') 084 Map config = ADDON_CACHE[pluginName] 085 if (config) { 086 config.node = addon 087 config.prefix = nodeName 088 } 089 } 090 } 091 } 092 } 093 094 static void handleAddonsAtStartup(GriffonApplication app) { 095 LOG.info("Loading addons [START]") 096 app.event(GriffonApplication.Event.LOAD_ADDONS_START.name, [app]) 097 098 computeAddonCache(app) 099 for (config in getAddonCache().values()) { 100 handleAddon(app, config) 101 } 102 103 app.addonManager.addons.each {name, addon -> 104 try { 105 addon.addonPostInit(app) 106 } catch (MissingMethodException mme) { 107 if (mme.method != 'addonPostInit') throw mme 108 } 109 app.event(GriffonApplication.Event.LOAD_ADDON_END.name, [name, addon, app]) 110 if (LOG.infoEnabled) LOG.info("Loaded addon $name") 111 } 112 113 app.event(GriffonApplication.Event.LOAD_ADDONS_END.name, [app, app.addonManager.addons]) 114 LOG.info("Loading addons [END]") 115 } 116 117 private static void handleAddon(GriffonApplication app, Map config) { 118 try { 119 config.addonClass = Class.forName(config.className) 120 } catch (ClassNotFoundException cnfe) { 121 if (config.node) { 122 throw cnfe 123 } else { 124 // skip 125 return 126 } 127 } 128 129 if (FactoryBuilderSupport.isAssignableFrom(config.addonClass)) return 130 131 GriffonAddonDescriptor addonDescriptor = app.addonManager.findAddonDescriptor(config.name) 132 if (addonDescriptor) return 133 134 def obj = config.addonClass.newInstance() 135 GriffonAddon addon = obj instanceof GriffonAddon ? obj : new DefaultGriffonAddon(app, obj) 136 addonDescriptor = new DefaultGriffonAddonDescriptor(config.prefix, config.className, config.name, config.version, addon) 137 138 app.addonManager.registerAddon(addonDescriptor) 139 140 MetaClass addonMetaClass = obj.metaClass 141 if (!(obj instanceof GriffonAddon)) { 142 addonMetaClass.app = app 143 addonMetaClass.newInstance = GriffonApplicationHelper.&newInstance.curry(app) 144 } 145 if (!(obj instanceof ThreadingHandler)) UIThreadManager.enhance(addonMetaClass) 146 147 if (LOG.infoEnabled) LOG.info("Loading addon ${config.name} with class ${addon.class.name}") 148 app.event(GriffonApplication.Event.LOAD_ADDON_START.name, [config.name, addon, app]) 149 150 addon.addonInit(app) 151 addMVCGroups(app, addon.mvcGroups) 152 addEvents(app, addon.events) 153 } 154 155 static void handleAddonsForBuilders(GriffonApplication app, UberBuilder builder, Map<String, MetaClass> targets) { 156 computeAddonCache(app) 157 for (config in getAddonCache().values()) { 158 handleAddonForBuilder(app, builder, targets, config) 159 } 160 161 app.addonManager.addons.each {name, addon -> 162 try { 163 addon.addonBuilderPostInit(app, builder) 164 } catch (MissingMethodException mme) { 165 if (mme.method != 'addonBuilderPostInit') throw mme 166 } 167 } 168 } 169 170 static void handleAddonForBuilder(GriffonApplication app, UberBuilder builder, Map<String, MetaClass> targets, Map addonConfig) { 171 try { 172 addonConfig.addonClass = Class.forName(addonConfig.className) 173 } catch (ClassNotFoundException cnfe) { 174 if (addonConfig.node) { 175 throw cnfe 176 } else { 177 // skip 178 return 179 } 180 } 181 182 if (FactoryBuilderSupport.isAssignableFrom(addonConfig.addonClass)) return 183 184 String addonName = addonConfig.name 185 String prefix = addonConfig.prefix 186 GriffonAddon addon = app.addonManager.findAddon(addonName) 187 188 addon.addonBuilderInit(app, builder) 189 190 DELEGATE_TYPES.each { String delegateType -> 191 List<Closure> delegates = addon."$delegateType" 192 delegateType = delegateType[0].toUpperCase() + delegateType[1..-2] 193 delegates.each { Closure delegateValue -> 194 builder."add$delegateType"(delegateValue) 195 } 196 } 197 198 Map factories = addon.factories 199 addFactories(builder, factories, addonName, prefix) 200 201 Map methods = addon.methods 202 addMethods(builder, methods, addonName, prefix) 203 204 Map props = addon.props 205 addProperties(builder, props, addonName, prefix) 206 207 for (partialTarget in addonConfig.node?.value) { 208 if (partialTarget.key == 'view') { 209 // this needs special handling, skip it for now 210 continue 211 } 212 MetaClass mc = targets[partialTarget.key] 213 if (!mc) continue 214 def values = partialTarget.value 215 if (values instanceof String) values = [partialTarget.value] 216 for (String itemName in values) { 217 if (itemName == '*') { 218 if (methods && LOG.traceEnabled) LOG.trace("Injecting all methods on $partialTarget.key") 219 _addMethods(mc, methods, prefix) 220 if (factories && LOG.traceEnabled) LOG.trace("Injecting all factories on $partialTarget.key") 221 _addFactories(mc, factories, prefix, builder) 222 if (props && LOG.traceEnabled) LOG.trace("Injecting all properties on $partialTarget.key") 223 _addProps(mc, props, prefix) 224 continue 225 } else if (itemName == '*:methods') { 226 if (methods && LOG.traceEnabled) LOG.trace("Injecting all methods on $partialTarget.key") 227 _addMethods(mc, methods, prefix) 228 continue 229 } else if (itemName == '*:factories') { 230 if (factories && LOG.traceEnabled) LOG.trace("Injecting all factories on $partialTarget.key") 231 _addFactories(mc, factories, prefix, builder) 232 continue 233 } else if (itemName == '*:props') { 234 if (props && LOG.traceEnabled) LOG.trace("Injecting all properties on $partialTarget.key") 235 _addProps(mc, props, prefix) 236 continue 237 } 238 239 def resolvedName = prefix + itemName 240 if (methods.containsKey(itemName)) { 241 if (LOG.traceEnabled) LOG.trace("Injected method ${resolvedName}() on $partialTarget.key") 242 mc."$resolvedName" = methods[itemName] 243 } else if (props.containsKey(itemName)) { 244 Map accessors = props[itemName] 245 String beanName 246 if (itemName.length() > 1) { 247 beanName = itemName[0].toUpperCase() + itemName.substring(1) 248 } else { 249 beanName = itemName[0].toUpperCase() 250 } 251 if (accessors.containsKey('get')) { 252 if (LOG.traceEnabled) LOG.trace("Injected getter for ${beanName} on $partialTarget.key") 253 mc."get$beanName" = accessors['get'] 254 } 255 if (accessors.containsKey('set')) { 256 if (LOG.traceEnabled) LOG.trace("Injected setter for ${beanName} on $partialTarget.key") 257 mc."set$beanName" = accessors['set'] 258 } 259 } else if (factories.containsKey(itemName)) { 260 if (LOG.traceEnabled) LOG.trace("Injected factory ${resolvedName} on $partialTarget.key") 261 mc."${resolvedName}" = {Object... args -> builder."$resolvedName"(* args)} 262 } 263 } 264 } 265 } 266 267 private static void _addMethods(MetaClass mc, Map methods, String prefix) { 268 methods.each { mk, mv -> mc."${prefix}${mk}" = mv } 269 } 270 271 private static void _addFactories(MetaClass mc, Map factories, String prefix, UberBuilder builder) { 272 factories.each { fk, fv -> 273 def resolvedName = prefix + fk 274 mc."$resolvedName" = {Object... args -> builder."$resolvedName"(* args) } 275 } 276 } 277 278 private static void _addProps(MetaClass mc, Map props, String prefix) { 279 props.each { pk, accessors -> 280 String beanName 281 if (pk.length() > 1) { 282 beanName = pk[0].toUpperCase() + pk.substring(1) 283 } else { 284 beanName = pk[0].toUpperCase() 285 } 286 if (accessors.containsKey('get')) mc."get$beanName" = accessors['get'] 287 if (accessors.containsKey('set')) mc."set$beanName" = accessors['set'] 288 } 289 } 290 291 static void addMVCGroups(GriffonApplication app, Map<String, Map<String, Object>> groups) { 292 Map<String, ConfigObject> mvcGroups = (Map<String, ConfigObject>) groups; 293 for (Map.Entry<String, ConfigObject> groupEntry: mvcGroups.entrySet()) { 294 String type = groupEntry.getKey(); 295 if (LOG.isDebugEnabled()) { 296 LOG.debug("Adding MVC group " + type); 297 } 298 ConfigObject members = groupEntry.getValue(); 299 Map<String, Object> configMap = new LinkedHashMap<String, Object>(); 300 Map<String, String> membersCopy = new LinkedHashMap<String, String>(); 301 for (Object o: members.entrySet()) { 302 Map.Entry entry = (Map.Entry) o; 303 String key = String.valueOf(entry.getKey()); 304 if ("config".equals(key) && entry.getValue() instanceof Map) { 305 configMap = (Map<String, Object>) entry.getValue(); 306 } else { 307 membersCopy.put(key, String.valueOf(entry.getValue())); 308 } 309 } 310 MVCGroupConfiguration configuration = app.getMvcGroupManager().newMVCGroupConfiguration(type, membersCopy, configMap); 311 app.getMvcGroupManager().addConfiguration(configuration); 312 } 313 } 314 315 static void addFactories(UberBuilder builder, Map<String, Object> factories, String addonName, String prefix) { 316 for (Map.Entry<String, Object> entry: factories.entrySet()) { 317 CompositeBuilderHelper.addFactory(builder, addonName, prefix + entry.getKey(), entry.getValue()); 318 } 319 } 320 321 static void addMethods(UberBuilder builder, Map<String, Closure> methods, String addonName, String prefix) { 322 for (Map.Entry<String, Closure> entry: methods.entrySet()) { 323 CompositeBuilderHelper.addMethod(builder, addonName, prefix + entry.getKey(), entry.getValue()); 324 } 325 } 326 327 static void addProperties(UberBuilder builder, Map<String, Map<String, Closure>> props, String addonName, String prefix) { 328 for (Map.Entry<String, Map<String, Closure>> entry: props.entrySet()) { 329 CompositeBuilderHelper.addProperty(builder, addonName, prefix + entry.getKey(), entry.getValue().get("get"), entry.getValue().get("set")); 330 } 331 } 332 333 static void addEvents(GriffonApplication app, Map<String, Closure> events) { 334 for (Map.Entry<String, Closure> entry: events.entrySet()) { 335 app.addApplicationEventListener(entry.getKey(), entry.getValue()); 336 } 337 } 338 }`