`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 == 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 }`