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