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.infoEnabledLOG.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 (addonDescriptorreturn
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.infoEnabledLOG.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 (!mccontinue
214             def values = partialTarget.value
215             if (values instanceof Stringvalues = [partialTarget.value]
216             for (String itemName in values) {
217                 if (itemName == '*') {
218                     if (methods && LOG.traceEnabledLOG.trace("Injecting all methods on $partialTarget.key")
219                     _addMethods(mc, methods, prefix)
220                     if (factories && LOG.traceEnabledLOG.trace("Injecting all factories on $partialTarget.key")
221                     _addFactories(mc, factories, prefix, builder)
222                     if (props && LOG.traceEnabledLOG.trace("Injecting all properties on $partialTarget.key")
223                     _addProps(mc, props, prefix)
224                     continue
225                 else if (itemName == '*:methods') {
226                     if (methods && LOG.traceEnabledLOG.trace("Injecting all methods on $partialTarget.key")
227                     _addMethods(mc, methods, prefix)
228                     continue
229                 else if (itemName == '*:factories') {
230                     if (factories && LOG.traceEnabledLOG.trace("Injecting all factories on $partialTarget.key")
231                     _addFactories(mc, factories, prefix, builder)
232                     continue
233                 else if (itemName == '*:props') {
234                     if (props && LOG.traceEnabledLOG.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.traceEnabledLOG.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.traceEnabledLOG.trace("Injected getter for ${beanName} on $partialTarget.key")
253                         mc."get$beanName" = accessors['get']
254                     }
255                     if (accessors.containsKey('set')) {
256                         if (LOG.traceEnabledLOG.trace("Injected setter for ${beanName} on $partialTarget.key")
257                         mc."set$beanName" = accessors['set']
258                     }
259                 else if (factories.containsKey(itemName)) {
260                     if (LOG.traceEnabledLOG.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.Entryo;
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 }