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.infoEnabled) LOG.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.addonClass) return
125
126 if (FactoryBuilderSupport.isAssignableFrom(config.addonClass)) return
127
128 GriffonAddonDescriptor addonDescriptor = app.addonManager.findAddonDescriptor(config.name)
129 if (addonDescriptor) return
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.infoEnabled) LOG.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.addonClass) return
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 (!mc) continue
232 def values = partialTarget.value
233 if (values instanceof String) values = [partialTarget.value]
234 for (String itemName in values) {
235 if (itemName == '*') {
236 if (methods && LOG.traceEnabled) LOG.trace("Injecting all methods on $partialTarget.key")
237 _addMethods(mc, methods, prefix)
238 if (factories && LOG.traceEnabled) LOG.trace("Injecting all factories on $partialTarget.key")
239 _addFactories(mc, factories, prefix, builder)
240 if (props && LOG.traceEnabled) LOG.trace("Injecting all properties on $partialTarget.key")
241 _addProps(mc, props, prefix)
242 continue
243 } else if (itemName == '*:methods') {
244 if (methods && LOG.traceEnabled) LOG.trace("Injecting all methods on $partialTarget.key")
245 _addMethods(mc, methods, prefix)
246 continue
247 } else if (itemName == '*:factories') {
248 if (factories && LOG.traceEnabled) LOG.trace("Injecting all factories on $partialTarget.key")
249 _addFactories(mc, factories, prefix, builder)
250 continue
251 } else if (itemName == '*:props') {
252 if (props && LOG.traceEnabled) LOG.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.traceEnabled) LOG.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.traceEnabled) LOG.trace("Injected getter for ${beanName} on $partialTarget.key")
271 mc."get$beanName" = accessors['get']
272 }
273 if (accessors.containsKey('set')) {
274 if (LOG.traceEnabled) LOG.trace("Injected setter for ${beanName} on $partialTarget.key")
275 mc."set$beanName" = accessors['set']
276 }
277 } else if (factories.containsKey(itemName)) {
278 if (LOG.traceEnabled) LOG.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.Entry) o;
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 }
|