AddonHelper.groovy
001 /*
002  * Copyright 2009-2013 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 org.slf4j.Logger
020 import org.slf4j.LoggerFactory
021 
022 import griffon.util.ApplicationClassLoader
023 import griffon.util.GriffonNameUtils
024 import groovy.transform.Synchronized
025 import org.codehaus.griffon.runtime.builder.CompositeBuilderHelper
026 import org.codehaus.griffon.runtime.builder.UberBuilder
027 import org.codehaus.griffon.runtime.core.DefaultGriffonAddon
028 import org.codehaus.griffon.runtime.core.DefaultGriffonAddonDescriptor
029 import griffon.core.*
030 
031 import static griffon.util.GriffonClassUtils.getGetterName
032 import static griffon.util.GriffonClassUtils.getSetterName
033 import static griffon.util.GriffonNameUtils.getClassNameForLowerCaseHyphenSeparatedName
034 
035 /**
036  * Helper class for dealing with addon initialization.
037  *
038  @author Danno Ferrin
039  @author Andres Almiray
040  */
041 class AddonHelper {
042     private static final Logger LOG = LoggerFactory.getLogger(AddonHelper)
043 
044     private static final Map<String, Map<String, Object>> ADDON_CACHE = [:]
045 
046     static final DELEGATE_TYPES = Collections.unmodifiableList([
047             "attributeDelegates",
048             "preInstantiateDelegates",
049             "postInstantiateDelegates",
050             "postNodeCompletionDelegates"
051     ])
052 
053     @Synchronized
054     private static Map<String, Map<String, Object>> getAddonCache() {
055         ADDON_CACHE
056     }
057 
058     @Synchronized
059     private static void computeAddonCache(GriffonApplication app) {
060         if (!ADDON_CACHE.isEmpty()) return
061 
062         // Load addons in order
063         URL addonMetadata = ApplicationClassLoader.get().getResource('META-INF/griffon-addons.properties')
064         if (addonMetadata) {
065             addonMetadata.text.eachLine line ->
066                 String[] parts = line.split('=')
067                 String pluginName = parts[0].trim()
068                 ADDON_CACHE[pluginName[
069                         node: null,
070                         version: parts[1].trim(),
071                         prefix: '',
072                         name: pluginName,
073                         className: getClassNameForLowerCaseHyphenSeparatedName(pluginName'GriffonAddon'
074                 ]
075             }
076         }
077 
078         for (node in app.builderConfig) {
079             String nodeName = node.key
080             switch (nodeName) {
081                 case 'addons':
082                 case 'features':
083                     // reserved words, not addon prefixes
084                     break
085                 default:
086                     if (nodeName == 'root') nodeName = ''
087                     node.value.each addon ->
088                         String pluginName = GriffonNameUtils.getHyphenatedName(addon.key - 'GriffonAddon')
089                         Map config = ADDON_CACHE[pluginName]
090                         if (config) {
091                             config.node = addon
092                             config.prefix = nodeName
093                         }
094                     }
095             }
096         }
097     }
098 
099     static void handleAddonsAtStartup(GriffonApplication app) {
100         LOG.info("Loading addons [START]")
101         app.event(GriffonApplication.Event.LOAD_ADDONS_START.name, [app])
102 
103         computeAddonCache(app)
104         for (config in getAddonCache().values()) {
105             handleAddon(app, config)
106         }
107 
108         app.addonManager.addons.each {name, addon ->
109             try {
110                 addon.addonPostInit(app)
111             catch (MissingMethodException mme) {
112                 if (mme.method != 'addonPostInit') throw mme
113             }
114             app.event(GriffonApplication.Event.LOAD_ADDON_END.name, [name, addon, app])
115             if (LOG.infoEnabledLOG.info("Loaded addon $name")
116         }
117 
118         app.event(GriffonApplication.Event.LOAD_ADDONS_END.name, [app, app.addonManager.addons])
119         LOG.info("Loading addons [END]")
120     }
121 
122     private static void handleAddon(GriffonApplication app, Map config) {
123         resolveAddonClass(config)
124         if (!config.addonClassreturn
125 
126         if (FactoryBuilderSupport.isAssignableFrom(config.addonClass)) return
127 
128         GriffonAddonDescriptor addonDescriptor = app.addonManager.findAddonDescriptor(config.name)
129         if (addonDescriptorreturn
130 
131         def obj = config.addonClass.newInstance()
132         GriffonAddon addon = obj instanceof GriffonAddon ? obj : new DefaultGriffonAddon(app, obj)
133         addonDescriptor = new DefaultGriffonAddonDescriptor(config.prefix, config.className, config.name, config.version, addon)
134 
135         app.addonManager.registerAddon(addonDescriptor)
136 
137         MetaClass addonMetaClass = obj.metaClass
138         if (!(obj instanceof GriffonAddon)) {
139             addonMetaClass.app = app
140             addonMetaClass.newInstance = GriffonApplicationHelper.&newInstance.curry(app)
141         }
142         if (!(obj instanceof ThreadingHandler)) UIThreadManager.enhance(addonMetaClass)
143 
144         if (LOG.infoEnabledLOG.info("Loading addon ${config.name} with class ${addon.class.name}")
145         app.event(GriffonApplication.Event.LOAD_ADDON_START.name, [config.name, addon, app])
146 
147         addon.addonInit(app)
148         addMVCGroups(app, getAddonPropertyAsMap(addon, 'mvcGroups'))
149         addEvents(app, getAddonPropertyAsMap(addon, 'events'))
150     }
151 
152     static void handleAddonsForBuilders(GriffonApplication app, UberBuilder builder, Map<String, MetaClass> targets) {
153         computeAddonCache(app)
154         for (config in getAddonCache().values()) {
155             handleAddonForBuilder(app, builder, targets, config)
156         }
157 
158         app.addonManager.addons.each {name, addon ->
159             try {
160                 addon.addonBuilderPostInit(app, builder)
161             catch (MissingMethodException mme) {
162                 if (mme.method != 'addonBuilderPostInit') throw mme
163             }
164         }
165     }
166 
167     private static void resolveAddonClass(Map config) {
168         String className = config.className
169 
170         if (!className.contains(".")) {
171             String fixedClassName = 'addon.' + className
172             try {
173                 config.addonClass = ApplicationClassLoader.get().loadClass(fixedClassName)
174                 config.className = fixedClassName
175             catch (ClassNotFoundException cnfe) {
176                 try {
177                     config.addonClass = ApplicationClassLoader.get().loadClass(className)
178                 catch (ClassNotFoundException cnfe2) {
179                     if (config.node) {
180                         throw cnfe2
181                     }
182                 }
183 
184             }
185         else {
186             try {
187                 config.addonClass = ApplicationClassLoader.get().loadClass(className)
188             catch (ClassNotFoundException cnfe) {
189                 if (config.node) {
190                     throw cnfe
191                 }
192             }
193         }
194     }
195 
196     static void handleAddonForBuilder(GriffonApplication app, UberBuilder builder, Map<String, MetaClass> targets, Map addonConfig) {
197         resolveAddonClass(addonConfig)
198         if (!addonConfig.addonClassreturn
199 
200         if (FactoryBuilderSupport.isAssignableFrom(addonConfig.addonClass)) return
201 
202         String addonName = addonConfig.name
203         String prefix = addonConfig.prefix
204         GriffonAddon addon = app.addonManager.findAddon(addonName)
205 
206         addon.addonBuilderInit(app, builder)
207 
208         DELEGATE_TYPES.each String delegateType ->
209             List<Closure> delegates = getAddonPropertyAsList(addon, delegateType)
210             delegateType = delegateType[0].toUpperCase() + delegateType[1..-2]
211             delegates.each Closure delegateValue ->
212                 builder."add$delegateType"(delegateValue)
213             }
214         }
215 
216         Map factories = getAddonPropertyAsMap(addon, 'factories')
217         addFactories(builder, factories, addonName, prefix)
218 
219         Map methods = getAddonPropertyAsMap(addon, 'methods')
220         addMethods(builder, methods, addonName, prefix)
221 
222         Map props = getAddonPropertyAsMap(addon, 'props')
223         addProperties(builder, props, addonName, prefix)
224 
225         for (partialTarget in addonConfig.node?.value) {
226             if (partialTarget.key == 'view') {
227                 // this needs special handling, skip it for now
228                 continue
229             }
230             MetaClass mc = targets[partialTarget.key]
231             if (!mccontinue
232             def values = partialTarget.value
233             if (values instanceof Stringvalues = [partialTarget.value]
234             for (String itemName in values) {
235                 if (itemName == '*') {
236                     if (methods && LOG.traceEnabledLOG.trace("Injecting all methods on $partialTarget.key")
237                     _addMethods(mc, methods, prefix)
238                     if (factories && LOG.traceEnabledLOG.trace("Injecting all factories on $partialTarget.key")
239                     _addFactories(mc, factories, prefix, builder)
240                     if (props && LOG.traceEnabledLOG.trace("Injecting all properties on $partialTarget.key")
241                     _addProps(mc, props, prefix)
242                     continue
243                 else if (itemName == '*:methods') {
244                     if (methods && LOG.traceEnabledLOG.trace("Injecting all methods on $partialTarget.key")
245                     _addMethods(mc, methods, prefix)
246                     continue
247                 else if (itemName == '*:factories') {
248                     if (factories && LOG.traceEnabledLOG.trace("Injecting all factories on $partialTarget.key")
249                     _addFactories(mc, factories, prefix, builder)
250                     continue
251                 else if (itemName == '*:props') {
252                     if (props && LOG.traceEnabledLOG.trace("Injecting all properties on $partialTarget.key")
253                     _addProps(mc, props, prefix)
254                     continue
255                 }
256 
257                 def resolvedName = prefix + itemName
258                 if (methods.containsKey(itemName)) {
259                     if (LOG.traceEnabledLOG.trace("Injected method ${resolvedName}() on $partialTarget.key")
260                     mc."$resolvedName" = methods[itemName]
261                 else if (props.containsKey(itemName)) {
262                     Map accessors = props[itemName]
263                     String beanName
264                     if (itemName.length() 1) {
265                         beanName = itemName[0].toUpperCase() + itemName.substring(1)
266                     else {
267                         beanName = itemName[0].toUpperCase()
268                     }
269                     if (accessors.containsKey('get')) {
270                         if (LOG.traceEnabledLOG.trace("Injected getter for ${beanName} on $partialTarget.key")
271                         mc."get$beanName" = accessors['get']
272                     }
273                     if (accessors.containsKey('set')) {
274                         if (LOG.traceEnabledLOG.trace("Injected setter for ${beanName} on $partialTarget.key")
275                         mc."set$beanName" = accessors['set']
276                     }
277                 else if (factories.containsKey(itemName)) {
278                     if (LOG.traceEnabledLOG.trace("Injected factory ${resolvedName} on $partialTarget.key")
279                     mc."${resolvedName}" {Object... args -> builder."$resolvedName"(* args)}
280                 }
281             }
282         }
283     }
284 
285     private static void _addMethods(MetaClass mc, Map methods, String prefix) {
286         methods.each mk, mv -> mc."${prefix}${mk}" = mv }
287     }
288 
289     private static void _addFactories(MetaClass mc, Map factories, String prefix, UberBuilder builder) {
290         factories.each fk, fv ->
291             def resolvedName = prefix + fk
292             mc."$resolvedName" {Object... args -> builder."$resolvedName"(* args) }
293         }
294     }
295 
296     private static void _addProps(MetaClass mc, Map props, String prefix) {
297         props.each beanName, accessors ->
298             if (accessors.containsKey('get')) mc."${getGetterName(beanName)}" = accessors['get']
299             if (accessors.containsKey('set')) mc."s${getSetterName(beanName)}" = accessors['set']
300         }
301     }
302 
303     static void addMVCGroups(GriffonApplication app, Map<String, Map<String, Object>> groups) {
304         Map<String, Map<String, Object>> mvcGroups = (Map<String, Map<String, Object>>groups;
305         for (Map.Entry<String, Map<String, Object>> groupEntry : mvcGroups.entrySet()) {
306             String type = groupEntry.getKey();
307             if (LOG.isDebugEnabled()) {
308                 LOG.debug("Adding MVC group " + type);
309             }
310             Map<String, Object> members = groupEntry.getValue();
311             Map<String, Object> configMap = new LinkedHashMap<String, Object>();
312             Map<String, String> membersCopy = new LinkedHashMap<String, String>();
313             for (Object o : members.entrySet()) {
314                 Map.Entry entry = (Map.Entryo;
315                 String key = String.valueOf(entry.getKey());
316                 if ("config".equals(key&& entry.getValue() instanceof Map) {
317                     configMap = (Map<String, Object>entry.getValue();
318                 else {
319                     membersCopy.put(key, String.valueOf(entry.getValue()));
320                 }
321             }
322             MVCGroupConfiguration configuration = app.getMvcGroupManager().newMVCGroupConfiguration(type, membersCopy, configMap);
323             app.getMvcGroupManager().addConfiguration(configuration);
324         }
325     }
326 
327     static void addFactories(UberBuilder builder, Map<String, Object> factories, String addonName, String prefix) {
328         for (Map.Entry<String, Object> entry : factories.entrySet()) {
329             CompositeBuilderHelper.addFactory(builder, addonName, prefix + entry.getKey(), entry.getValue());
330         }
331     }
332 
333     static void addMethods(UberBuilder builder, Map<String, Closure> methods, String addonName, String prefix) {
334         for (Map.Entry<String, Closure> entry : methods.entrySet()) {
335             CompositeBuilderHelper.addMethod(builder, addonName, prefix + entry.getKey(), entry.getValue());
336         }
337     }
338 
339     static void addProperties(UberBuilder builder, Map<String, Map<String, Closure>> props, String addonName, String prefix) {
340         for (Map.Entry<String, Map<String, Closure>> entry : props.entrySet()) {
341             CompositeBuilderHelper.addProperty(builder, addonName, prefix + entry.getKey(), entry.getValue().get("get"), entry.getValue().get("set"));
342         }
343     }
344 
345     static void addEvents(GriffonApplication app, Map<String, Closure> events) {
346         for (Map.Entry<String, Closure> entry : events.entrySet()) {
347             app.addApplicationEventListener(entry.getKey(), entry.getValue());
348         }
349     }
350 
351     private static Map getAddonPropertyAsMap(GriffonAddon addon, String propertyName) {
352         Map property = [:]
353 
354         try {
355             // property access
356             property = addon[propertyName]
357             if (property != null && !property.isEmpty()) return property
358         catch (Exception e) {
359             // ignore
360         }
361 
362         try {
363             // invoke getter
364             property = addon."${getGetterName(propertyName)}"()
365             if (property != null && !property.isEmpty()) return property
366         catch (Exception e) {
367             // ignore
368         }
369 
370         try {
371             // direct field access
372             property = addon.@"$propertyName"
373             if (property != null && !property.isEmpty()) return property
374         catch (Exception e) {
375             // ignore
376         }
377 
378         return [:]
379     }
380 
381     private static List getAddonPropertyAsList(GriffonAddon addon, String propertyName) {
382         List property = []
383 
384         try {
385             // property access
386             property = addon[propertyName]
387             if (property != null && !property.isEmpty()) return property
388         catch (Exception e) {
389             // ignore
390         }
391 
392         try {
393             // invoke getter
394             property = addon."${getGetterName(propertyName)}"()
395             if (property != null && !property.isEmpty()) return property
396         catch (Exception e) {
397             // ignore
398         }
399 
400         try {
401             // direct field access
402             property = addon.@"$propertyName"
403             if (property != null && !property.isEmpty()) return property
404         catch (Exception e) {
405             // ignore
406         }
407 
408         return []
409     }
410 }