DefaultMVCGroupManager.java
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.core;
018 
019 import griffon.core.*;
020 import griffon.exceptions.MVCGroupInstantiationException;
021 import griffon.util.ApplicationClassLoader;
022 import griffon.util.CollectionUtils;
023 import groovy.lang.GroovySystem;
024 import groovy.lang.MetaClass;
025 import groovy.lang.MissingMethodException;
026 import groovy.lang.Script;
027 import groovy.util.FactoryBuilderSupport;
028 import org.codehaus.groovy.runtime.InvokerHelper;
029 import org.slf4j.Logger;
030 import org.slf4j.LoggerFactory;
031 
032 import java.util.Collections;
033 import java.util.LinkedHashMap;
034 import java.util.Map;
035 
036 import static griffon.util.ConfigUtils.getConfigValueAsBoolean;
037 import static griffon.util.ConfigUtils.getConfigValueAsString;
038 import static griffon.util.GriffonExceptionHandler.sanitize;
039 import static griffon.util.GriffonNameUtils.isBlank;
040 import static java.util.Arrays.asList;
041 import static org.codehaus.griffon.runtime.builder.CompositeBuilderHelper.createBuilder;
042 import static org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.castToBoolean;
043 
044 /**
045  * Base implementation of the {@code MVCGroupManager} interface.
046  *
047  @author Andres Almiray
048  @since 0.9.4
049  */
050 public class DefaultMVCGroupManager extends AbstractMVCGroupManager {
051     private static final Logger LOG = LoggerFactory.getLogger(DefaultMVCGroupManager.class);
052     private static final String CONFIG_KEY_COMPONENT = "component";
053     private static final String CONFIG_KEY_EVENTS_LIFECYCLE = "events.lifecycle";
054     private static final String CONFIG_KEY_EVENTS_INSTANTIATION = "events.instantiation";
055     private static final String CONFIG_KEY_EVENTS_DESTRUCTION = "events.destruction";
056     private static final String CONFIG_KEY_EVENTS_LISTENER = "events.listener";
057     private static final Object[] EMPTY_ARGS = new Object[0];
058 
059     private static final String KEY_BUILDER = "builder";
060     private static final String KEY_MVC_GROUP_INIT = "mvcGroupInit";
061     private static final String KEY_MVC_GROUP_DESTROY = "mvcGroupDestroy";
062     // private static final String KEY_GRIFFON_DESTROY = "griffonDestroy";
063 
064     public DefaultMVCGroupManager(GriffonApplication app) {
065         super(app);
066     }
067 
068     public MVCGroupConfiguration newMVCGroupConfiguration(String mvcType, Map<String, String> members, Map<String, Object> config) {
069         return new DefaultMVCGroupConfiguration(getApp(), mvcType, members, config);
070     }
071 
072     public MVCGroup newMVCGroup(MVCGroupConfiguration configuration, String mvcId, Map<String, Object> members) {
073         return new DefaultMVCGroup(getApp(), configuration, mvcId, members);
074     }
075 
076     protected void doInitialize(Map<String, MVCGroupConfiguration> configurations) {
077         for (MVCGroupConfiguration configuration : configurations.values()) {
078             addConfiguration(configuration);
079         }
080     }
081 
082     protected MVCGroup buildMVCGroup(MVCGroupConfiguration configuration, String mvcId, Map<String, Object> args) {
083         if (args == nullargs = Collections.EMPTY_MAP;
084 
085         boolean component = castToBoolean(configuration.getConfig().get(CONFIG_KEY_COMPONENT));
086         boolean checkId = true;
087 
088         if (isBlank(mvcId)) {
089             if (component) {
090                 checkId = false;
091             else {
092                 mvcId = configuration.getMvcType();
093             }
094         }
095 
096         if (checkIdcheckIdIsUnique(mvcId, configuration);
097 
098         if (LOG.isInfoEnabled())
099             LOG.info("Building MVC group '" + configuration.getMvcType() "' with name '" + mvcId + "'");
100         Map<String, Object> argsCopy = copyAndConfigureArguments(args, configuration, mvcId);
101 
102         // figure out what the classes are and prep the metaclass
103         Map<String, MetaClass> metaClassMap = new LinkedHashMap<String, MetaClass>();
104         Map<String, Class> klassMap = new LinkedHashMap<String, Class>();
105         Map<String, GriffonClass> griffonClassMap = new LinkedHashMap<String, GriffonClass>();
106         for (Map.Entry<String, String> memberEntry : configuration.getMembers().entrySet()) {
107             String memberType = memberEntry.getKey();
108             String memberClassName = memberEntry.getValue();
109             selectClassesPerMember(memberType, memberClassName, klassMap, metaClassMap, griffonClassMap);
110         }
111 
112         // create the builder
113         FactoryBuilderSupport builder = createBuilder(getApp(), metaClassMap);
114 
115         boolean isEventPublishingEnabled = getApp().isEventPublishingEnabled();
116         getApp().setEventPublishingEnabled(isConfigFlagEnabled(configuration, CONFIG_KEY_EVENTS_INSTANTIATION));
117         Map<String, Object> instances = null;
118         try {
119             instances = instantiateMembers(klassMap, argsCopy, griffonClassMap, builder);
120         finally {
121             getApp().setEventPublishingEnabled(isEventPublishingEnabled);
122         }
123 
124         instances.put(KEY_BUILDER, builder);
125         argsCopy.put(KEY_BUILDER, builder);
126 
127         MVCGroup group = newMVCGroup(configuration, mvcId, instances);
128         // must set it again because mvcId might have been initialized internally
129         argsCopy.put("mvcName", group.getMvcId());
130         argsCopy.put("mvcId", group.getMvcId());
131         argsCopy.put("mvcGroup", group);
132 
133         for (Map.Entry<String, Object> variable : argsCopy.entrySet()) {
134             builder.setVariable(variable.getKey(), variable.getValue());
135         }
136 
137         boolean fireEvents = isConfigFlagEnabled(configuration, CONFIG_KEY_EVENTS_LIFECYCLE);
138         if (fireEvents) {
139             getApp().event(GriffonApplication.Event.INITIALIZE_MVC_GROUP.getName(), asList(configuration, group));
140         }
141 
142         // special case --
143         // controllers are added as application listeners
144         // addApplicationListener method is null safe
145         if (isConfigFlagEnabled(group.getConfiguration(), CONFIG_KEY_EVENTS_LISTENER)) {
146             getApp().addApplicationEventListener(group.getController());
147         }
148 
149         // mutually set each other to the available fields and inject args
150         fillReferencedProperties(group, argsCopy);
151 
152         if (checkIddoAddGroup(group);
153 
154         initializeMembers(group, argsCopy);
155 
156         if (fireEventsgetApp().event(GriffonApplication.Event.CREATE_MVC_GROUP.getName(), asList(group));
157 
158         return group;
159     }
160 
161     protected void selectClassesPerMember(String memberType, String memberClassName, Map<String, Class> klassMap, Map<String, MetaClass> metaClassMap, Map<String, GriffonClass> griffonClassMap) {
162         GriffonClass griffonClass = getApp().getArtifactManager().findGriffonClass(memberClassName);
163         Class klass = griffonClass != null ? griffonClass.getClazz() : loadClass(memberClassName);
164         MetaClass metaClass = griffonClass != null ? griffonClass.getMetaClass() : GroovySystem.getMetaClassRegistry().getMetaClass(klass);
165         klassMap.put(memberType, klass);
166         metaClassMap.put(memberType, metaClass);
167         griffonClassMap.put(memberType, griffonClass);
168     }
169 
170     protected Map<String, Object> copyAndConfigureArguments(Map<String, Object> args, MVCGroupConfiguration configuration, String mvcId) {
171         Map<String, Object> argsCopy = CollectionUtils.<String, Object>map()
172                 .e("app", getApp())
173                 .e("mvcType", configuration.getMvcType())
174                 .e("mvcName", mvcId)
175                 .e("mvcId", mvcId)
176                 .e("configuration", configuration);
177 
178         argsCopy.putAll(getApp().getBindings().getVariables());
179         argsCopy.putAll(args);
180         for (String methodName : UIThreadManager.THREADING_METHOD_NAMES) {
181             argsCopy.remove(methodName);
182         }
183         return argsCopy;
184     }
185 
186     protected void checkIdIsUnique(String mvcId, MVCGroupConfiguration configuration) {
187         if (findGroup(mvcId!= null) {
188             String action = getConfigValueAsString(getApp().getConfig()"griffon.mvcid.collision""exception");
189             if ("warning".equalsIgnoreCase(action)) {
190                 if (LOG.isWarnEnabled()) {
191                     LOG.warn("A previous instance of MVC group '" + configuration.getMvcType() "' with name '" + mvcId + "' exists. Destroying the old instance first.");
192                     destroyMVCGroup(mvcId);
193                 }
194             else {
195                 throw new MVCGroupInstantiationException("Can not instantiate MVC group '" + configuration.getMvcType() "' with name '" + mvcId + "' because a previous instance with that name exists and was not disposed off properly.", configuration.getMvcType(), mvcId);
196             }
197         }
198     }
199 
200     protected Map<String, Object> instantiateMembers(Map<String, Class> klassMap, Map<String, Object> args, Map<String, GriffonClass> griffonClassMap, FactoryBuilderSupport builder) {
201         // instantiate the parts
202         Map<String, Object> instanceMap = new LinkedHashMap<String, Object>();
203         for (Map.Entry<String, Class> classEntry : klassMap.entrySet()) {
204             String memberType = classEntry.getKey();
205             Class memberClass = classEntry.getValue();
206             if (args.containsKey(memberType)) {
207                 // use provided value, even if null
208                 instanceMap.put(memberType, args.get(memberType));
209             else {
210                 // otherwise create a new value
211                 GriffonClass griffonClass = griffonClassMap.get(memberType);
212                 Object instance = null;
213                 if (griffonClass != null) {
214                     instance = griffonClass.newInstance();
215                 else {
216                     instance = getApp().newInstance(memberClass, memberType);
217                 }
218                 instanceMap.put(memberType, instance);
219                 args.put(memberType, instance);
220 
221                 // all scripts get the builder as their binding
222                 if (instance instanceof Script) {
223                     builder.getVariables().putAll(((Scriptinstance).getBinding().getVariables());
224                     ((Scriptinstance).setBinding(builder);
225                 }
226             }
227         }
228         return instanceMap;
229     }
230 
231     protected void initializeMembers(MVCGroup group, Map<String, Object> args) {
232         // initialize the classes and call scripts
233         if (LOG.isDebugEnabled()) LOG.debug("Initializing each MVC member of group '" + group.getMvcId() "'");
234         for (Map.Entry<String, Object> memberEntry : group.getMembers().entrySet()) {
235             String memberType = memberEntry.getKey();
236             Object member = memberEntry.getValue();
237             if (member instanceof Script) {
238                 group.buildScriptMember(memberType);
239             else if (!KEY_BUILDER.equalsIgnoreCase(memberType)) {
240                 try {
241                     InvokerHelper.invokeMethod(member, KEY_MVC_GROUP_INIT, new Object[]{args});
242                 catch (MissingMethodException mme) {
243                     if (!KEY_MVC_GROUP_INIT.equals(mme.getMethod())) {
244                         throw mme;
245                     }
246                     // MME on mvcGroupInit means they didn't define
247                     // an init method.  This is not an error.
248                 }
249             }
250         }
251     }
252 
253     protected void fillReferencedProperties(MVCGroup group, Map<String, Object> args) {
254         for (Object member : group.getMembers().values()) {
255             // loop on the instance map to get just the instances
256             if (member instanceof Script) {
257                 ((Scriptmember).getBinding().getVariables().putAll(args);
258             else {
259                 // set the args and instances
260                 InvokerHelper.setProperties(member, args);
261             }
262         }
263     }
264 
265     protected void doAddGroup(MVCGroup group) {
266         addGroup(group);
267     }
268 
269     public void destroyMVCGroup(String mvcId) {
270         MVCGroup group = findGroup(mvcId);
271         if (LOG.isDebugEnabled()) LOG.trace("Group '" + mvcId + "' points to " + group);
272         if (group == nullreturn;
273         if (LOG.isInfoEnabled()) LOG.info("Destroying MVC group identified by '" + mvcId + "'");
274 
275         if (isConfigFlagEnabled(group.getConfiguration(), CONFIG_KEY_EVENTS_LISTENER)) {
276             getApp().removeApplicationEventListener(group.getController());
277         }
278 
279         boolean fireDestructionEvents = isConfigFlagEnabled(group.getConfiguration(), CONFIG_KEY_EVENTS_DESTRUCTION);
280 
281         for (Map.Entry<String, Object> memberEntry : group.getMembers().entrySet()) {
282             String memberType = memberEntry.getKey();
283             if(KEY_BUILDER.equalsIgnoreCase(memberType)) continue;
284 
285             Object member = memberEntry.getValue();
286             if (member instanceof GriffonMvcArtifact) {
287                 GriffonMvcArtifact artifact = (GriffonMvcArtifactmember;
288                 if(fireDestructionEvents) {
289                     getApp().event(GriffonApplication.Event.DESTROY_INSTANCE.getName(), asList(member.getClass(), artifact.getGriffonClass().getArtifactType(), artifact));
290                 }
291                 artifact.mvcGroupDestroy();
292                 /*((GriffonMvcArtifact) member).griffonDestroy();
293             } else if (member instanceof GriffonArtifact) {
294                 ((GriffonArtifact) member).griffonDestroy();
295             */else if (member != null && !(member instanceof Script)) {
296                 try {
297                     InvokerHelper.invokeMethod(member, KEY_MVC_GROUP_DESTROY, EMPTY_ARGS);
298                 catch (MissingMethodException mme) {
299                     if (!KEY_MVC_GROUP_DESTROY.equals(mme.getMethod())) {
300                         throw mme;
301                     }
302                     // MME on mvcGroupDestroy means they didn't define
303                     // a destroy method.  This is not an error.
304                 }
305                 /*
306                 try {
307                     InvokerHelper.invokeMethod(member, KEY_GRIFFON_DESTROY, EMPTY_ARGS);
308                 } catch (MissingMethodException mme) {
309                     if (!KEY_GRIFFON_DESTROY.equals(mme.getMethod())) {
310                         throw mme;
311                     }
312                     // MME on griffonDestroy means they didn't define
313                     // a destroy method.  This is not an error.
314                 }
315                 */
316             }
317         }
318 
319         try {
320             if (group.getBuilder() != null) {
321                 group.getBuilder().dispose();
322                 group.getBuilder().getVariables().clear();
323             }
324         catch (MissingMethodException mme) {
325             // TODO find out why this call breaks applet mode on shutdown
326             if (LOG.isErrorEnabled())
327                 LOG.error("Application encountered an error while destroying group '" + mvcId + "'", sanitize(mme));
328         }
329 
330         doRemoveGroup(group);
331         group.destroy();
332 
333         if (isConfigFlagEnabled(group.getConfiguration(), CONFIG_KEY_EVENTS_LIFECYCLE)) {
334             getApp().event(GriffonApplication.Event.DESTROY_MVC_GROUP.getName(), asList(group));
335         }
336     }
337 
338     protected void doRemoveGroup(MVCGroup group) {
339         removeGroup(group);
340     }
341 
342     protected Class loadClass(String className) {
343         try {
344             return ApplicationClassLoader.get().loadClass(className);
345         catch (ClassNotFoundException e) {
346             // ignored
347         }
348         return null;
349     }
350 
351     protected boolean isConfigFlagEnabled(MVCGroupConfiguration configuration, String key) {
352         return getConfigValueAsBoolean(configuration.getConfig(), key, true);
353     }
354 }