GriffonApplicationHelper.java
001 /*
002  * Copyright 2008-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 package org.codehaus.griffon.runtime.util;
017 
018 import griffon.core.*;
019 import griffon.core.factories.AddonManagerFactory;
020 import griffon.core.factories.ArtifactManagerFactory;
021 import griffon.core.factories.MVCGroupManagerFactory;
022 import griffon.exceptions.GriffonException;
023 import griffon.util.*;
024 import groovy.lang.*;
025 import groovy.util.ConfigObject;
026 import groovy.util.ConfigSlurper;
027 import org.apache.log4j.LogManager;
028 import org.apache.log4j.helpers.LogLog;
029 import org.codehaus.griffon.runtime.core.ControllerArtifactHandler;
030 import org.codehaus.griffon.runtime.core.ModelArtifactHandler;
031 import org.codehaus.griffon.runtime.core.ServiceArtifactHandler;
032 import org.codehaus.griffon.runtime.core.ViewArtifactHandler;
033 import org.codehaus.griffon.runtime.logging.Log4jConfig;
034 import org.codehaus.groovy.runtime.InvokerHelper;
035 import org.slf4j.Logger;
036 import org.slf4j.LoggerFactory;
037 
038 import java.io.IOException;
039 import java.io.InputStream;
040 import java.lang.reflect.Constructor;
041 import java.net.URL;
042 import java.util.Enumeration;
043 import java.util.LinkedHashMap;
044 import java.util.Map;
045 import java.util.Properties;
046 
047 import static griffon.util.ConfigUtils.getConfigValueAsString;
048 import static griffon.util.GriffonExceptionHandler.sanitize;
049 import static griffon.util.GriffonNameUtils.isBlank;
050 import static java.util.Arrays.asList;
051 import static org.codehaus.groovy.runtime.DefaultGroovyMethods.eachLine;
052 
053 /**
054  * Utility class for bootstrapping an application and handling of MVC groups.</p>
055  *
056  @author Danno Ferrin
057  @author Andres Almiray
058  */
059 public class GriffonApplicationHelper {
060     private static final Logger LOG = LoggerFactory.getLogger(GriffonApplicationHelper.class);
061 
062     private static final Map<String, String> DEFAULT_PLATFORM_HANDLERS = CollectionUtils.<String, String>map()
063             .e("linux""org.codehaus.griffon.runtime.util.DefaultLinuxPlatformHandler")
064             .e("macosx""org.codehaus.griffon.runtime.util.DefaultMacOSXPlatformHandler")
065             .e("solaris""org.codehaus.griffon.runtime.util.DefaultSolarisPlatformHandler")
066             .e("windows""org.codehaus.griffon.runtime.util.DefaultWindowsPlatformHandler");
067 
068     static {
069         ExpandoMetaClassCreationHandle.enable();
070     }
071 
072     /**
073      * Creates, register and assigns an ExpandoMetaClass for a target class.<p>
074      * The newly created metaClass will accept changes after initialization.
075      *
076      @param clazz the target class
077      @return an ExpandoMetaClass
078      */
079     public static MetaClass expandoMetaClassFor(Class clazz) {
080         MetaClass mc = GroovySystem.getMetaClassRegistry().getMetaClass(clazz);
081         if (!(mc instanceof ExpandoMetaClass)) {
082             mc = new ExpandoMetaClass(clazz, true, true);
083             mc.initialize();
084             GroovySystem.getMetaClassRegistry().setMetaClass(clazz, mc);
085         }
086         return mc;
087     }
088 
089     private static ConfigObject loadConfig(ConfigSlurper configSlurper, Class configClass, String configFileName) {
090         ConfigObject config = new ConfigObject();
091         try {
092             if (configClass != null) {
093                 config.merge(configSlurper.parse(configClass));
094             }
095             InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(configFileName + ".properties");
096             if (is != null) {
097                 Properties p = new Properties();
098                 p.load(is);
099                 config.merge(configSlurper.parse(p));
100             }
101         catch (Exception x) {
102             LogLog.warn("Cannot read configuration [class: " + configClass + ", file: " + configFileName + "]", sanitize(x));
103         }
104         return config;
105     }
106 
107     /**
108      * Setups an application.<p>
109      * This method performs the following tasks<ul>
110      <li>Sets "griffon.start.dir" as system property.</li>
111      <li>Calls the Initialize life cycle script.</li>
112      <li>Reads runtime and builder configuration.</li>
113      <li>Setups basic artifact handlers.</li>
114      <li>Initializes available addons.</li>
115      </ul>
116      *
117      @param app the current Griffon application
118      */
119     public static void prepare(GriffonApplication app) {
120         app.getBindings().setVariable("app", app);
121 
122         Metadata.getCurrent().getGriffonStartDir();
123         Metadata.getCurrent().getGriffonWorkingDir();
124 
125         readAndSetConfiguration(app);
126         app.event(GriffonApplication.Event.BOOTSTRAP_START.getName(), asList(app));
127 
128         applyPlatformTweaks(app);
129         runLifecycleHandler(GriffonApplication.Lifecycle.INITIALIZE.getName(), app);
130         initializeArtifactManager(app);
131         initializeMvcManager(app);
132         initializeAddonManager(app);
133 
134         app.event(GriffonApplication.Event.BOOTSTRAP_END.getName(), asList(app));
135     }
136 
137     private static void readAndSetConfiguration(GriffonApplication app) {
138         ConfigSlurper configSlurper = new ConfigSlurper(Environment.getCurrent().getName());
139         app.setConfig(loadConfig(configSlurper, app.getAppConfigClass(), GriffonApplication.Configuration.APPLICATION.getName()));
140         app.getConfig().merge(loadConfig(configSlurper, app.getConfigClass(), GriffonApplication.Configuration.CONFIG.getName()));
141         GriffonExceptionHandler.configure(app.getConfig().flatten(new LinkedHashMap()));
142 
143         app.setBuilderConfig(loadConfig(configSlurper, app.getBuilderClass(), GriffonApplication.Configuration.BUILDER.getName()));
144 
145         Object events = safeNewInstance(app.getEventsClass());
146         if (events != null) {
147             app.setEventsConfig(events);
148             app.addApplicationEventListener(app.getEventsConfig());
149         }
150 
151         Object log4jConfig = app.getConfig().get("log4j");
152         if (log4jConfig instanceof Closure) {
153             app.event(GriffonApplication.Event.LOG4J_CONFIG_START.getName(), asList(log4jConfig));
154             LogManager.resetConfiguration();
155             new Log4jConfig().configure((Closurelog4jConfig);
156         }
157     }
158 
159     public static void applyPlatformTweaks(GriffonApplication app) {
160         String platform = GriffonApplicationUtils.platform;
161         String handlerClassName = getConfigValueAsString(app.getConfig()"platform.handler." + platform, DEFAULT_PLATFORM_HANDLERS.get(platform));
162         PlatformHandler platformHandler = (PlatformHandlersafeNewInstance(handlerClassName);
163         platformHandler.handle(app);
164     }
165 
166     private static final String KEY_ARTIFACT_MANAGER_FACTORY = "app.artifactManager.factory";
167     private static final String DEFAULT_ARTIFACT_MANAGER_FACTORY = "org.codehaus.griffon.runtime.core.factories.DefaultArtifactManagerFactory";
168 
169     private static void initializeArtifactManager(GriffonApplication app) {
170         if (app.getArtifactManager() == null) {
171             String className = getConfigValueAsString(app.getConfig(), KEY_ARTIFACT_MANAGER_FACTORY, DEFAULT_ARTIFACT_MANAGER_FACTORY);
172             if (LOG.isDebugEnabled()) {
173                 LOG.debug("Using " + className + " as ArtifactManagerFactory");
174             }
175             ArtifactManagerFactory factory = (ArtifactManagerFactorysafeNewInstance(className);
176             InvokerHelper.setProperty(app, "artifactManager", factory.create(app));
177         }
178 
179         // initialize default Artifact handlers
180         app.getArtifactManager().registerArtifactHandler(new ModelArtifactHandler(app));
181         app.getArtifactManager().registerArtifactHandler(new ViewArtifactHandler(app));
182         app.getArtifactManager().registerArtifactHandler(new ControllerArtifactHandler(app));
183         app.getArtifactManager().registerArtifactHandler(new ServiceArtifactHandler(app));
184 
185         // load additional handlers
186         loadArtifactHandlers(app);
187 
188         app.getArtifactManager().loadArtifactMetadata();
189     }
190 
191     private static final String KEY_ADDON_MANAGER_FACTORY = "app.addonManager.factory";
192     private static final String DEFAULT_ADDON_MANAGER_FACTORY = "org.codehaus.griffon.runtime.core.factories.DefaultAddonManagerFactory";
193 
194     private static void initializeAddonManager(GriffonApplication app) {
195         if (app.getAddonManager() == null) {
196             String className = getConfigValueAsString(app.getConfig(), KEY_ADDON_MANAGER_FACTORY, DEFAULT_ADDON_MANAGER_FACTORY);
197             if (LOG.isDebugEnabled()) {
198                 LOG.debug("Using " + className + " as AddonManagerFactory");
199             }
200             AddonManagerFactory factory = (AddonManagerFactorysafeNewInstance(className);
201             InvokerHelper.setProperty(app, "addonManager", factory.create(app));
202         }
203         app.getAddonManager().initialize();
204     }
205 
206     private static final String KEY_MVCGROUP_MANAGER_FACTORY = "app.mvcGroupManager.factory";
207     private static final String DEFAULT_MVCGROUP_MANAGER_FACTORY = "org.codehaus.griffon.runtime.core.factories.DefaultMVCGroupManagerFactory";
208 
209     private static void initializeMvcManager(GriffonApplication app) {
210         if (app.getMvcGroupManager() == null) {
211             String className = getConfigValueAsString(app.getConfig(), KEY_MVCGROUP_MANAGER_FACTORY, DEFAULT_MVCGROUP_MANAGER_FACTORY);
212             if (LOG.isDebugEnabled()) {
213                 LOG.debug("Using " + className + " as MVCGroupManagerFactory");
214             }
215             MVCGroupManagerFactory factory = (MVCGroupManagerFactorysafeNewInstance(className);
216             InvokerHelper.setProperty(app, "mvcGroupManager", factory.create(app));
217         }
218 
219         Map<String, MVCGroupConfiguration> configurations = new LinkedHashMap<String, MVCGroupConfiguration>();
220         Map<String, ConfigObject> mvcGroups = (Map<String, ConfigObject>app.getConfig().get("mvcGroups");
221         if (mvcGroups != null) {
222             for (Map.Entry<String, ConfigObject> groupEntry : mvcGroups.entrySet()) {
223                 String type = groupEntry.getKey();
224                 if (LOG.isDebugEnabled()) {
225                     LOG.debug("Adding MVC group " + type);
226                 }
227                 ConfigObject members = groupEntry.getValue();
228                 Map<String, Object> configMap = new LinkedHashMap<String, Object>();
229                 Map<String, String> membersCopy = new LinkedHashMap<String, String>();
230                 for (Object o : members.entrySet()) {
231                     Map.Entry entry = (Map.Entryo;
232                     String key = String.valueOf(entry.getKey());
233                     if ("config".equals(key&& entry.getValue() instanceof Map) {
234                         configMap = (Map<String, Object>entry.getValue();
235                     else {
236                         membersCopy.put(key, String.valueOf(entry.getValue()));
237                     }
238                 }
239                 configurations.put(type, app.getMvcGroupManager().newMVCGroupConfiguration(type, membersCopy, configMap));
240             }
241         }
242 
243         app.getMvcGroupManager().initialize(configurations);
244     }
245 
246     private static void loadArtifactHandlers(final GriffonApplication app) {
247         Enumeration<URL> urls = null;
248 
249         try {
250             urls = app.getClass().getClassLoader().getResources("META-INF/services/" + ArtifactHandler.class.getName());
251         catch (IOException ioe) {
252             return;
253         }
254 
255         if (urls == nullreturn;
256 
257         while (urls.hasMoreElements()) {
258             URL url = urls.nextElement();
259             if (LOG.isDebugEnabled()) {
260                 LOG.debug("Reading " + ArtifactHandler.class.getName() " definitions from " + url);
261             }
262 
263             try {
264                 eachLine(url, new RunnableWithArgsClosure(new RunnableWithArgs() {
265                     @Override
266                     public void run(Object[] args) {
267                         String line = (Stringargs[0];
268                         if (line.startsWith("#"|| isBlank(line)) return;
269                         try {
270                             Class artifactHandlerClass = loadClass(line);
271                             Constructor ctor = artifactHandlerClass.getDeclaredConstructor(GriffonApplication.class);
272                             ArtifactHandler handler = null;
273                             if (ctor != null) {
274                                 handler = (ArtifactHandlerctor.newInstance(app);
275                             else {
276                                 handler = (ArtifactHandlersafeNewInstance(artifactHandlerClass);
277                             }
278                             app.getArtifactManager().registerArtifactHandler(handler);
279                         catch (Exception e) {
280                             if (LOG.isWarnEnabled()) {
281                                 LOG.warn("Could not load ArtifactHandler with " + line, sanitize(e));
282                             }
283                         }
284                     }
285                 }));
286             catch (IOException e) {
287                 if (LOG.isWarnEnabled()) {
288                     LOG.warn("Could not load ArtifactHandler from " + url, sanitize(e));
289                 }
290             }
291         }
292     }
293 
294     /**
295      * Executes a script inside the UI Thread.<p>
296      * On Swing this would be the Event Dispatch Thread.
297      *
298      @deprecated use runLifecycleHandler instead
299      */
300     @Deprecated
301     public static void runScriptInsideUIThread(String scriptName, GriffonApplication app) {
302         runLifecycleHandler(scriptName, app);
303     }
304 
305     /**
306      * Executes a script inside the UI Thread.<p>
307      * On Swing this would be the Event Dispatch Thread.
308      */
309     public static void runLifecycleHandler(String handlerName, GriffonApplication app) {
310         Class<?> handlerClass = null;
311         try {
312             handlerClass = loadClass(handlerName);
313         catch (ClassNotFoundException cnfe) {
314             if (cnfe.getMessage().equals(handlerName)) {
315                 // the script must not exist, do nothing
316                 //LOGME - may be because of chained failures
317                 return;
318             else {
319                 throw new GriffonException(cnfe);
320             }
321         }
322 
323         if (Script.class.isAssignableFrom(handlerClass)) {
324             doRunScript(handlerName, handlerClass, app);
325         else if (LifecycleHandler.class.isAssignableFrom(handlerClass)) {
326             doRunLifecycleHandler(handlerName, handlerClass, app);
327         }
328     }
329 
330     private static void doRunScript(String scriptName, Class handlerClass, GriffonApplication app) {
331         Script script = (ScriptsafeNewInstance(handlerClass);
332         script.setBinding(app.getBindings());
333         UIThreadManager.enhance(script);
334         if (LOG.isInfoEnabled()) {
335             LOG.info("Running lifecycle handler (script) '" + scriptName + "'");
336         }
337         UIThreadManager.getInstance().executeSync(script);
338     }
339 
340     private static void doRunLifecycleHandler(String handlerName, Class handlerClass, GriffonApplication app) {
341         LifecycleHandler handler = (LifecycleHandlersafeNewInstance(handlerClass);
342         if (LOG.isInfoEnabled()) {
343             LOG.info("Running lifecycle handler (class) '" + handlerName + "'");
344         }
345         UIThreadManager.getInstance().executeSync(handler);
346     }
347 
348     /**
349      * Creates a new instance of the specified class.<p>
350      * Publishes a <strong>NewInstance</strong> event with the following arguments<ul>
351      <li>klass - the target Class</li>
352      <li>type - the type of the instance (i.e, 'controller','service')</li>
353      <li>instance - the newly created instance</li>
354      </ul>
355      *
356      @param app   the current GriffonApplication
357      @param klass the target Class from which the instance will be created
358      @return a newly created instance of type klass
359      */
360     public static Object newInstance(GriffonApplication app, Class klass) {
361         return newInstance(app, klass, "");
362     }
363 
364     /**
365      * Creates a new instance of the specified class.<p>
366      * Publishes a <strong>NewInstance</strong> event with the following arguments<ul>
367      <li>klass - the target Class</li>
368      <li>type - the type of the instance (i.e, 'controller','service')</li>
369      <li>instance - the newly created instance</li>
370      </ul>
371      *
372      @param app   the current GriffonApplication
373      @param klass the target Class from which the instance will be created
374      @param type  optional type parameter, used when publishing a 'NewInstance' event
375      @return a newly created instance of type klass
376      */
377     public static Object newInstance(GriffonApplication app, Class klass, String type) {
378         if (isBlank(type)) type = "";
379         if (LOG.isDebugEnabled()) {
380             LOG.debug("Instantiating " + klass.getName() " with type '" + type + "'");
381         }
382 
383         Object instance = safeNewInstance(klass);
384 
385         GriffonClass griffonClass = app.getArtifactManager().findGriffonClass(klass);
386         MetaClass mc = griffonClass != null ? griffonClass.getMetaClass() : expandoMetaClassFor(klass);
387         enhance(app, klass, mc, instance);
388 
389         app.event(GriffonApplication.Event.NEW_INSTANCE.getName(), asList(klass, type, instance));
390         return instance;
391     }
392 
393     public static void enhance(GriffonApplication app, Class klass, MetaClass mc, Object instance) {
394         try {
395             InvokerHelper.invokeMethod(instance, "setApp", app);
396         catch (MissingMethodException mme) {
397             try {
398                 InvokerHelper.setProperty(instance, "app", app);
399             catch (MissingPropertyException mpe) {
400                 if (mc instanceof ExpandoMetaClass) {
401                     ((ExpandoMetaClassmc).registerBeanProperty("app", app);
402                 }
403             }
404         }
405 
406         if (!GriffonArtifact.class.isAssignableFrom(klass)) {
407             /*
408             mc.createMVCGroup = {Object...args - >
409                     app.mvcGroupManager.createMVCGroup( * args)
410             }
411             mc.buildMVCGroup = {Object...args - >
412                     app.mvcGroupManager.buildMVCGroup( * args)
413             }
414             mc.destroyMVCGroup = {String mvcName - >
415                     app.mvcGroupManager.destroyMVCGroup(mvcName)
416             }
417             mc.withMVCGroup = {Object...args - >
418                     app.mvcGroupManager.withMVCGroup( * args)
419             }
420             mc.newInstance = {Object...args - >
421                     GriffonApplicationHelper.newInstance(app, * args)
422             }
423             */
424             UIThreadManager.enhance(mc);
425         }
426     }
427 
428     public static Class loadClass(String classNamethrows ClassNotFoundException {
429         ClassNotFoundException cnfe = null;
430 
431         ClassLoader cl = GriffonApplicationHelper.class.getClassLoader();
432         try {
433             return cl.loadClass(className);
434         catch (ClassNotFoundException e) {
435             cnfe = e;
436         }
437 
438         cl = Thread.currentThread().getContextClassLoader();
439         try {
440             return cl.loadClass(className);
441         catch (ClassNotFoundException e) {
442             cnfe = e;
443         }
444 
445         if (cnfe != nullthrow cnfe;
446         return null;
447     }
448 
449     private static Object safeNewInstance(String className) {
450         try {
451             return loadClass(className).newInstance();
452         catch (Exception e) {
453             return null;
454         }
455     }
456 
457     private static Object safeNewInstance(Class clazz) {
458         try {
459             return clazz.newInstance();
460         catch (Exception e) {
461             return null;
462         }
463     }
464 }