001 /*
002 * Copyright 2008-2014 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 package org.codehaus.griffon.runtime.core.mvc;
017
018 import griffon.core.ApplicationClassLoader;
019 import griffon.core.ApplicationEvent;
020 import griffon.core.GriffonApplication;
021 import griffon.core.artifact.ArtifactManager;
022 import griffon.core.artifact.GriffonArtifact;
023 import griffon.core.artifact.GriffonClass;
024 import griffon.core.artifact.GriffonController;
025 import griffon.core.artifact.GriffonMvcArtifact;
026 import griffon.core.artifact.GriffonView;
027 import griffon.core.mvc.MVCGroup;
028 import griffon.core.mvc.MVCGroupConfiguration;
029 import griffon.exceptions.MVCGroupInstantiationException;
030 import griffon.exceptions.NewInstanceException;
031 import griffon.util.CollectionUtils;
032 import org.slf4j.Logger;
033 import org.slf4j.LoggerFactory;
034
035 import javax.annotation.Nonnull;
036 import javax.annotation.Nullable;
037 import javax.inject.Inject;
038 import java.util.LinkedHashMap;
039 import java.util.Map;
040
041 import static griffon.core.GriffonExceptionHandler.sanitize;
042 import static griffon.util.ConfigUtils.getConfigValueAsBoolean;
043 import static griffon.util.GriffonClassUtils.setPropertiesOrFieldsNoException;
044 import static griffon.util.GriffonNameUtils.capitalize;
045 import static griffon.util.GriffonNameUtils.isBlank;
046 import static java.util.Arrays.asList;
047 import static java.util.Objects.requireNonNull;
048
049 /**
050 * Base implementation of the {@code MVCGroupManager} interface.
051 *
052 * @author Andres Almiray
053 * @since 2.0.0
054 */
055 public class DefaultMVCGroupManager extends AbstractMVCGroupManager {
056 private static final Logger LOG = LoggerFactory.getLogger(DefaultMVCGroupManager.class);
057 private static final String CONFIG_KEY_COMPONENT = "component";
058 private static final String CONFIG_KEY_EVENTS_LIFECYCLE = "events.lifecycle";
059 private static final String CONFIG_KEY_EVENTS_INSTANTIATION = "events.instantiation";
060 private static final String CONFIG_KEY_EVENTS_DESTRUCTION = "events.destruction";
061 private static final String CONFIG_KEY_EVENTS_LISTENER = "events.listener";
062 private static final String KEY_PARENT_GROUP = "parentGroup";
063
064 private final ApplicationClassLoader applicationClassLoader;
065
066 @Inject
067 public DefaultMVCGroupManager(@Nonnull GriffonApplication application, @Nonnull ApplicationClassLoader applicationClassLoader) {
068 super(application);
069 this.applicationClassLoader = requireNonNull(applicationClassLoader, "Argument 'applicationClassLoader' must not be null");
070 }
071
072 @Nonnull
073 public MVCGroupConfiguration newMVCGroupConfiguration(@Nonnull String mvcType, @Nonnull Map<String, String> members, @Nonnull Map<String, Object> config) {
074 return new DefaultMVCGroupConfiguration(this, mvcType, members, config);
075 }
076
077 @Nonnull
078 public MVCGroup newMVCGroup(@Nonnull MVCGroupConfiguration configuration, @Nullable String mvcId, @Nonnull Map<String, Object> members) {
079 return new DefaultMVCGroup(this, configuration, mvcId, members);
080 }
081
082 protected void doInitialize(@Nonnull Map<String, MVCGroupConfiguration> configurations) {
083 requireNonNull(configurations, "Argument 'configurations' must not be null");
084 for (MVCGroupConfiguration configuration : configurations.values()) {
085 addConfiguration(configuration);
086 }
087 }
088
089 @Nonnull
090 protected MVCGroup createMVCGroup(@Nonnull MVCGroupConfiguration configuration, @Nullable String mvcId, @Nonnull Map<String, Object> args) {
091 requireNonNull(configuration, ERROR_CONFIGURATION_NULL);
092 requireNonNull(args, ERROR_ARGS_NULL);
093
094 mvcId = resolveMvcId(configuration, mvcId);
095 checkIdIsUnique(mvcId, configuration);
096
097 LOG.debug("Building MVC group '{}' with name '{}'", configuration.getMvcType(), mvcId);
098 Map<String, Object> argsCopy = copyAndConfigureArguments(args, configuration, mvcId);
099
100 // figure out what the classes are
101 Map<String, ClassHolder> classMap = new LinkedHashMap<>();
102 for (Map.Entry<String, String> memberEntry : configuration.getMembers().entrySet()) {
103 String memberType = memberEntry.getKey();
104 String memberClassName = memberEntry.getValue();
105 selectClassesPerMember(memberType, memberClassName, classMap);
106 }
107
108 boolean isEventPublishingEnabled = getApplication().getEventRouter().isEventPublishingEnabled();
109 getApplication().getEventRouter().setEventPublishingEnabled(isConfigFlagEnabled(configuration, CONFIG_KEY_EVENTS_INSTANTIATION));
110 Map<String, Object> instances = new LinkedHashMap<>();
111 try {
112 instances.putAll(instantiateMembers(classMap, argsCopy));
113 } finally {
114 getApplication().getEventRouter().setEventPublishingEnabled(isEventPublishingEnabled);
115 }
116
117 MVCGroup group = newMVCGroup(configuration, mvcId, instances);
118 adjustMvcArguments(group, argsCopy);
119
120 boolean fireEvents = isConfigFlagEnabled(configuration, CONFIG_KEY_EVENTS_LIFECYCLE);
121 if (fireEvents) {
122 getApplication().getEventRouter().publishEvent(ApplicationEvent.INITIALIZE_MVC_GROUP.getName(), asList(configuration, group));
123 }
124
125 // special case -- controllers are added as application listeners
126 if (isConfigFlagEnabled(group.getConfiguration(), CONFIG_KEY_EVENTS_LISTENER)) {
127 GriffonController controller = group.getController();
128 if (controller != null) {
129 getApplication().getEventRouter().addEventListener(controller);
130 }
131 }
132
133 // mutually set each other to the available fields and inject args
134 fillReferencedProperties(group, argsCopy);
135
136 doAddGroup(group);
137
138 initializeMembers(group, argsCopy);
139
140 if (fireEvents) {
141 getApplication().getEventRouter().publishEvent(ApplicationEvent.CREATE_MVC_GROUP.getName(), asList(group));
142 }
143
144 return group;
145 }
146
147 protected void adjustMvcArguments(@Nonnull MVCGroup group, @Nonnull Map<String, Object> args) {
148 // must set it again because mvcId might have been initialized internally
149 args.put("mvcId", group.getMvcId());
150 args.put("mvcGroup", group);
151 args.put("application", getApplication());
152 }
153
154 @Nonnull
155 @SuppressWarnings("ConstantConditions")
156 protected String resolveMvcId(@Nonnull MVCGroupConfiguration configuration, @Nullable String mvcId) {
157 boolean component = getConfigValueAsBoolean(configuration.getConfig(), CONFIG_KEY_COMPONENT, false);
158
159 if (isBlank(mvcId)) {
160 if (component) {
161 mvcId = configuration.getMvcType() + "-" + System.nanoTime();
162 } else {
163 mvcId = configuration.getMvcType();
164 }
165 }
166 return mvcId;
167 }
168
169 @SuppressWarnings("unchecked")
170 protected void selectClassesPerMember(@Nonnull String memberType, @Nonnull String memberClassName, @Nonnull Map<String, ClassHolder> classMap) {
171 GriffonClass griffonClass = getApplication().getArtifactManager().findGriffonClass(memberClassName);
172 ClassHolder classHolder = new ClassHolder();
173 if (griffonClass != null) {
174 classHolder.artifactClass = (Class<? extends GriffonArtifact>) griffonClass.getClazz();
175 } else {
176 classHolder.regularClass = loadClass(memberClassName);
177 }
178 classMap.put(memberType, classHolder);
179 }
180
181 @Nonnull
182 protected Map<String, Object> copyAndConfigureArguments(@Nonnull Map<String, Object> args, @Nonnull MVCGroupConfiguration configuration, @Nonnull String mvcId) {
183 Map<String, Object> argsCopy = CollectionUtils.<String, Object>map()
184 .e("application", getApplication())
185 .e("mvcType", configuration.getMvcType())
186 .e("mvcId", mvcId)
187 .e("configuration", configuration);
188
189 if (args.containsKey(KEY_PARENT_GROUP)) {
190 if (args.get(KEY_PARENT_GROUP) instanceof MVCGroup) {
191 MVCGroup parentGroup = (MVCGroup) args.get(KEY_PARENT_GROUP);
192 for (Map.Entry<String, Object> e : parentGroup.getMembers().entrySet()) {
193 args.put("parent" + capitalize(e.getKey()), e.getValue());
194 }
195 }
196 }
197
198 argsCopy.putAll(args);
199 return argsCopy;
200 }
201
202 protected void checkIdIsUnique(@Nonnull String mvcId, @Nonnull MVCGroupConfiguration configuration) {
203 if (findGroup(mvcId) != null) {
204 String action = getApplication().getConfiguration().getAsString("griffon.mvcid.collision", "exception");
205 if ("warning".equalsIgnoreCase(action)) {
206 LOG.warn("A previous instance of MVC group '{}' with name '{}' exists. Destroying the old instance first.", configuration.getMvcType(), mvcId);
207 destroyMVCGroup(mvcId);
208 } else {
209 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);
210 }
211 }
212 }
213
214 @Nonnull
215 protected Map<String, Object> instantiateMembers(@Nonnull Map<String, ClassHolder> classMap, @Nonnull Map<String, Object> args) {
216 // instantiate the parts
217 Map<String, Object> instanceMap = new LinkedHashMap<>();
218 for (Map.Entry<String, ClassHolder> classEntry : classMap.entrySet()) {
219 String memberType = classEntry.getKey();
220 if (args.containsKey(memberType)) {
221 // use provided value, even if null
222 instanceMap.put(memberType, args.get(memberType));
223 } else {
224 // otherwise create a new value
225 ClassHolder classHolder = classEntry.getValue();
226 if (classHolder.artifactClass != null) {
227 Class<? extends GriffonArtifact> memberClass = classHolder.artifactClass;
228 ArtifactManager artifactManager = getApplication().getArtifactManager();
229 GriffonClass griffonClass = artifactManager.findGriffonClass(memberClass);
230 GriffonArtifact instance = artifactManager.newInstance(griffonClass);
231 instanceMap.put(memberType, instance);
232 args.put(memberType, instance);
233 } else {
234 Class<?> memberClass = classHolder.regularClass;
235 try {
236 Object instance = memberClass.newInstance();
237 instanceMap.put(memberType, instance);
238 args.put(memberType, instance);
239 } catch (InstantiationException | IllegalAccessException e) {
240 LOG.error("Can't create member {} with {}", memberType, memberClass);
241 throw new NewInstanceException(memberClass, e);
242 }
243 }
244 }
245 }
246 return instanceMap;
247 }
248
249 protected void initializeMembers(@Nonnull MVCGroup group, @Nonnull Map<String, Object> args) {
250 LOG.debug("Initializing each MVC member of group '{}'", group.getMvcId());
251 for (Map.Entry<String, Object> memberEntry : group.getMembers().entrySet()) {
252 String memberType = memberEntry.getKey();
253 Object member = memberEntry.getValue();
254 if (member instanceof GriffonArtifact) {
255 initializeArtifactMember(group, memberType, (GriffonArtifact) member, args);
256 } else {
257 initializeNonArtifactMember(group, memberType, member, args);
258 }
259 }
260 }
261
262 protected void initializeArtifactMember(@Nonnull MVCGroup group, @Nonnull String type, final @Nonnull GriffonArtifact member, final @Nonnull Map<String, Object> args) {
263 if (member instanceof GriffonView) {
264 getApplication().getUIThreadManager().runInsideUISync(new Runnable() {
265 @Override
266 public void run() {
267 try {
268 GriffonView view = (GriffonView) member;
269 view.initUI();
270 view.mvcGroupInit(args);
271 } catch (RuntimeException e) {
272 throw (RuntimeException) sanitize(e);
273 }
274 }
275 });
276 } else if (member instanceof GriffonMvcArtifact) {
277 ((GriffonMvcArtifact) member).mvcGroupInit(args);
278 }
279 }
280
281 protected void initializeNonArtifactMember(@Nonnull MVCGroup group, @Nonnull String type, @Nonnull Object member, @Nonnull Map<String, Object> args) {
282 // empty
283 }
284
285 protected void fillReferencedProperties(@Nonnull MVCGroup group, @Nonnull Map<String, Object> args) {
286 for (Map.Entry<String, Object> memberEntry : group.getMembers().entrySet()) {
287 String memberType = memberEntry.getKey();
288 Object member = memberEntry.getValue();
289 if (member instanceof GriffonArtifact) {
290 fillArtifactMemberProperties(memberType, (GriffonArtifact) member, args);
291 } else {
292 fillNonArtifactMemberProperties(memberType, member, args);
293 }
294 }
295 }
296
297 private void fillArtifactMemberProperties(@Nonnull String type, @Nonnull GriffonArtifact member, @Nonnull Map<String, Object> args) {
298 // set the args and instances
299 setPropertiesOrFieldsNoException(member, args);
300 }
301
302 protected void fillNonArtifactMemberProperties(@Nonnull String type, @Nonnull Object member, @Nonnull Map<String, Object> args) {
303 // empty
304 }
305
306 protected void doAddGroup(@Nonnull MVCGroup group) {
307 addGroup(group);
308 }
309
310 public void destroyMVCGroup(@Nonnull String mvcId) {
311 MVCGroup group = findGroup(mvcId);
312 LOG.debug("Group '{}' points to {}", mvcId, group);
313
314 if (group == null) return;
315
316 LOG.debug("Destroying MVC group identified by '{}'", mvcId);
317
318 if (isConfigFlagEnabled(group.getConfiguration(), CONFIG_KEY_EVENTS_LISTENER)) {
319 GriffonController controller = group.getController();
320 if (controller != null) {
321 getApplication().getEventRouter().removeEventListener(controller);
322 }
323 }
324
325 boolean fireDestructionEvents = isConfigFlagEnabled(group.getConfiguration(), CONFIG_KEY_EVENTS_DESTRUCTION);
326
327 destroyMembers(group, fireDestructionEvents);
328
329 doRemoveGroup(group);
330 group.destroy();
331
332 if (isConfigFlagEnabled(group.getConfiguration(), CONFIG_KEY_EVENTS_LIFECYCLE)) {
333 getApplication().getEventRouter().publishEvent(ApplicationEvent.DESTROY_MVC_GROUP.getName(), asList(group));
334 }
335 }
336
337 protected void destroyMembers(@Nonnull MVCGroup group, boolean fireDestructionEvents) {
338 for (Map.Entry<String, Object> memberEntry : group.getMembers().entrySet()) {
339 if (memberEntry.getValue() instanceof GriffonArtifact) {
340 destroyArtifactMember(memberEntry.getKey(), (GriffonArtifact) memberEntry.getValue(), fireDestructionEvents);
341 } else {
342 destroyNonArtifactMember(memberEntry.getKey(), memberEntry.getValue(), fireDestructionEvents);
343 }
344 }
345 }
346
347 protected void destroyArtifactMember(@Nonnull String type, @Nonnull GriffonArtifact member, boolean fireDestructionEvents) {
348 if (member instanceof GriffonMvcArtifact) {
349 GriffonMvcArtifact artifact = (GriffonMvcArtifact) member;
350 if (fireDestructionEvents) {
351 getApplication().getEventRouter().publishEvent(ApplicationEvent.DESTROY_INSTANCE.getName(), asList(member.getClass(), artifact));
352 }
353 artifact.mvcGroupDestroy();
354 }
355 }
356
357 protected void destroyNonArtifactMember(@Nonnull String type, @Nonnull Object member, boolean fireDestructionEvents) {
358 // empty
359 }
360
361 protected void doRemoveGroup(@Nonnull MVCGroup group) {
362 removeGroup(group);
363 }
364
365 protected boolean isConfigFlagEnabled(@Nonnull MVCGroupConfiguration configuration, @Nonnull String key) {
366 return getConfigValueAsBoolean(configuration.getConfig(), key, true);
367 }
368
369 @Nullable
370 protected Class<?> loadClass(@Nonnull String className) {
371 try {
372 return applicationClassLoader.get().loadClass(className);
373 } catch (ClassNotFoundException e) {
374 // ignored
375 }
376 return null;
377 }
378
379 protected static final class ClassHolder {
380 protected Class<?> regularClass;
381 protected Class<? extends GriffonArtifact> artifactClass;
382 }
383 }
|