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 }
|