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 == null) args = 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 (checkId) checkIdIsUnique(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 (checkId) doAddGroup(group);
153
154 initializeMembers(group, argsCopy);
155
156 if (fireEvents) getApp().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(((Script) instance).getBinding().getVariables());
224 ((Script) instance).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 ((Script) member).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 == null) return;
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 = (GriffonMvcArtifact) member;
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 }
|