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((Closure) log4jConfig);
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 = (PlatformHandler) safeNewInstance(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 = (ArtifactManagerFactory) safeNewInstance(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 = (AddonManagerFactory) safeNewInstance(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 = (MVCGroupManagerFactory) safeNewInstance(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.Entry) o;
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 == null) return;
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 = (String) args[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 = (ArtifactHandler) ctor.newInstance(app);
275 } else {
276 handler = (ArtifactHandler) safeNewInstance(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 = (Script) safeNewInstance(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 = (LifecycleHandler) safeNewInstance(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 ((ExpandoMetaClass) mc).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 className) throws 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 != null) throw 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 }
|