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 == null) args = 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 (checkId) checkIdIsUnique(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 (checkId) doAddGroup(group);
148
149 initializeMembers(group, argsCopy);
150
151 if (fireEvents) getApp().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(((Script) instance).getBinding().getVariables());
216 ((Script) instance).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 ((Script) member).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 == null) return;
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 }
|