001 /*
002 * Copyright 2008-2016 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.FieldException;
030 import griffon.exceptions.GriffonException;
031 import griffon.exceptions.MVCGroupInstantiationException;
032 import griffon.exceptions.NewInstanceException;
033 import griffon.inject.Contextual;
034 import griffon.util.CollectionUtils;
035 import org.codehaus.griffon.runtime.core.injection.InjectionUnitOfWork;
036 import org.slf4j.Logger;
037 import org.slf4j.LoggerFactory;
038
039 import javax.annotation.Nonnull;
040 import javax.annotation.Nullable;
041 import javax.inject.Inject;
042 import java.beans.PropertyDescriptor;
043 import java.lang.reflect.Field;
044 import java.lang.reflect.InvocationTargetException;
045 import java.lang.reflect.Method;
046 import java.util.ArrayList;
047 import java.util.Arrays;
048 import java.util.LinkedHashMap;
049 import java.util.List;
050 import java.util.Map;
051
052 import static griffon.core.GriffonExceptionHandler.sanitize;
053 import static griffon.util.AnnotationUtils.annotationsOfMethodParameter;
054 import static griffon.util.AnnotationUtils.findAnnotation;
055 import static griffon.util.AnnotationUtils.nameFor;
056 import static griffon.util.AnnotationUtils.namesFor;
057 import static griffon.util.ConfigUtils.getConfigValueAsBoolean;
058 import static griffon.util.GriffonClassUtils.getAllDeclaredFields;
059 import static griffon.util.GriffonClassUtils.getPropertyDescriptors;
060 import static griffon.util.GriffonClassUtils.setFieldValue;
061 import static griffon.util.GriffonClassUtils.setPropertiesOrFieldsNoException;
062 import static griffon.util.GriffonClassUtils.setPropertyOrFieldValueNoException;
063 import static griffon.util.GriffonNameUtils.capitalize;
064 import static griffon.util.GriffonNameUtils.isBlank;
065 import static java.util.Arrays.asList;
066 import static java.util.Objects.requireNonNull;
067
068 /**
069 * Base implementation of the {@code MVCGroupManager} interface.
070 *
071 * @author Andres Almiray
072 * @since 2.0.0
073 */
074 public class DefaultMVCGroupManager extends AbstractMVCGroupManager {
075 private static final Logger LOG = LoggerFactory.getLogger(DefaultMVCGroupManager.class);
076 private static final String CONFIG_KEY_COMPONENT = "component";
077 private static final String CONFIG_KEY_EVENTS_LIFECYCLE = "events.lifecycle";
078 private static final String CONFIG_KEY_EVENTS_INSTANTIATION = "events.instantiation";
079 private static final String CONFIG_KEY_EVENTS_DESTRUCTION = "events.destruction";
080 private static final String CONFIG_KEY_EVENTS_LISTENER = "events.listener";
081 private static final String KEY_PARENT_GROUP = "parentGroup";
082
083 private final ApplicationClassLoader applicationClassLoader;
084
085 @Inject
086 public DefaultMVCGroupManager(@Nonnull GriffonApplication application, @Nonnull ApplicationClassLoader applicationClassLoader) {
087 super(application);
088 this.applicationClassLoader = requireNonNull(applicationClassLoader, "Argument 'applicationClassLoader' must not be null");
089 }
090
091 protected void doInitialize(@Nonnull Map<String, MVCGroupConfiguration> configurations) {
092 requireNonNull(configurations, "Argument 'configurations' must not be null");
093 for (MVCGroupConfiguration configuration : configurations.values()) {
094 addConfiguration(configuration);
095 }
096 }
097
098 @Nonnull
099 protected MVCGroup createMVCGroup(@Nonnull MVCGroupConfiguration configuration, @Nullable String mvcId, @Nonnull Map<String, Object> args) {
100 requireNonNull(configuration, ERROR_CONFIGURATION_NULL);
101 requireNonNull(args, ERROR_ARGS_NULL);
102
103 mvcId = resolveMvcId(configuration, mvcId);
104 checkIdIsUnique(mvcId, configuration);
105
106 LOG.debug("Building MVC group '{}' with name '{}'", configuration.getMvcType(), mvcId);
107 Map<String, Object> argsCopy = copyAndConfigureArguments(args, configuration, mvcId);
108
109 // figure out what the classes are
110 Map<String, ClassHolder> classMap = new LinkedHashMap<>();
111 for (Map.Entry<String, String> memberEntry : configuration.getMembers().entrySet()) {
112 String memberType = memberEntry.getKey();
113 String memberClassName = memberEntry.getValue();
114 selectClassesPerMember(memberType, memberClassName, classMap);
115 }
116
117 boolean isEventPublishingEnabled = getApplication().getEventRouter().isEventPublishingEnabled();
118 getApplication().getEventRouter().setEventPublishingEnabled(isConfigFlagEnabled(configuration, CONFIG_KEY_EVENTS_INSTANTIATION));
119 Map<String, Object> instances = new LinkedHashMap<>();
120 List<Object> injectedInstances = new ArrayList<>();
121
122 try {
123 InjectionUnitOfWork.start();
124 } catch (IllegalStateException ise) {
125 throw new MVCGroupInstantiationException("Can not instantiate MVC group '" + configuration.getMvcType() + "' with id '" + mvcId + "'", configuration.getMvcType(), mvcId, ise);
126 }
127
128 try {
129 instances.putAll(instantiateMembers(classMap, argsCopy));
130 } finally {
131 getApplication().getEventRouter().setEventPublishingEnabled(isEventPublishingEnabled);
132 try {
133 injectedInstances.addAll(InjectionUnitOfWork.finish());
134 } catch (IllegalStateException ise) {
135 throw new MVCGroupInstantiationException("Can not instantiate MVC group '" + configuration.getMvcType() + "' with id '" + mvcId + "'", configuration.getMvcType(), mvcId, ise);
136 }
137 }
138
139 MVCGroup group = newMVCGroup(configuration, mvcId, instances, (MVCGroup) args.get(KEY_PARENT_GROUP));
140 adjustMvcArguments(group, argsCopy);
141
142 boolean fireEvents = isConfigFlagEnabled(configuration, CONFIG_KEY_EVENTS_LIFECYCLE);
143 if (fireEvents) {
144 getApplication().getEventRouter().publishEvent(ApplicationEvent.INITIALIZE_MVC_GROUP.getName(), asList(configuration, group));
145 }
146
147 // special case -- controllers are added as application listeners
148 if (isConfigFlagEnabled(group.getConfiguration(), CONFIG_KEY_EVENTS_LISTENER)) {
149 GriffonController controller = group.getController();
150 if (controller != null) {
151 getApplication().getEventRouter().addEventListener(controller);
152 }
153 }
154
155 // mutually set each other to the available fields and inject args
156 fillReferencedProperties(group, argsCopy);
157
158 doAddGroup(group);
159
160 initializeMembers(group, argsCopy);
161 if (group instanceof AbstractMVCGroup) {
162 ((AbstractMVCGroup) group).getInjectedInstances().addAll(injectedInstances);
163 }
164
165 if (fireEvents) {
166 getApplication().getEventRouter().publishEvent(ApplicationEvent.CREATE_MVC_GROUP.getName(), asList(group));
167 }
168
169 return group;
170 }
171
172 protected void adjustMvcArguments(@Nonnull MVCGroup group, @Nonnull Map<String, Object> args) {
173 // must set it again because mvcId might have been initialized internally
174 args.put("mvcId", group.getMvcId());
175 args.put("mvcGroup", group);
176 args.put("application", getApplication());
177 }
178
179 @Nonnull
180 @SuppressWarnings("ConstantConditions")
181 protected String resolveMvcId(@Nonnull MVCGroupConfiguration configuration, @Nullable String mvcId) {
182 boolean component = getConfigValueAsBoolean(configuration.getConfig(), CONFIG_KEY_COMPONENT, false);
183
184 if (isBlank(mvcId)) {
185 if (component) {
186 mvcId = configuration.getMvcType() + "-" + System.nanoTime();
187 } else {
188 mvcId = configuration.getMvcType();
189 }
190 }
191 return mvcId;
192 }
193
194 @SuppressWarnings("unchecked")
195 protected void selectClassesPerMember(@Nonnull String memberType, @Nonnull String memberClassName, @Nonnull Map<String, ClassHolder> classMap) {
196 GriffonClass griffonClass = getApplication().getArtifactManager().findGriffonClass(memberClassName);
197 ClassHolder classHolder = new ClassHolder();
198 if (griffonClass != null) {
199 classHolder.artifactClass = (Class<? extends GriffonArtifact>) griffonClass.getClazz();
200 } else {
201 classHolder.regularClass = loadClass(memberClassName);
202 }
203 classMap.put(memberType, classHolder);
204 }
205
206 @Nonnull
207 protected Map<String, Object> copyAndConfigureArguments(@Nonnull Map<String, Object> args, @Nonnull MVCGroupConfiguration configuration, @Nonnull String mvcId) {
208 Map<String, Object> argsCopy = CollectionUtils.<String, Object>map()
209 .e("application", getApplication())
210 .e("mvcType", configuration.getMvcType())
211 .e("mvcId", mvcId)
212 .e("configuration", configuration);
213
214 if (args.containsKey(KEY_PARENT_GROUP)) {
215 if (args.get(KEY_PARENT_GROUP) instanceof MVCGroup) {
216 MVCGroup parentGroup = (MVCGroup) args.get(KEY_PARENT_GROUP);
217 for (Map.Entry<String, Object> e : parentGroup.getMembers().entrySet()) {
218 args.put("parent" + capitalize(e.getKey()), e.getValue());
219 }
220 }
221 }
222
223 argsCopy.putAll(args);
224 return argsCopy;
225 }
226
227 protected void checkIdIsUnique(@Nonnull String mvcId, @Nonnull MVCGroupConfiguration configuration) {
228 if (findGroup(mvcId) != null) {
229 String action = getApplication().getConfiguration().getAsString("griffon.mvcid.collision", "exception");
230 if ("warning".equalsIgnoreCase(action)) {
231 LOG.warn("A previous instance of MVC group '{}' with id '{}' exists. Destroying the old instance first.", configuration.getMvcType(), mvcId);
232 destroyMVCGroup(mvcId);
233 } else {
234 throw new MVCGroupInstantiationException("Can not instantiate MVC group '" + configuration.getMvcType() + "' with id '" + mvcId + "' because a previous instance with that name exists and was not disposed off properly.", configuration.getMvcType(), mvcId);
235 }
236 }
237 }
238
239 @Nonnull
240 protected Map<String, Object> instantiateMembers(@Nonnull Map<String, ClassHolder> classMap, @Nonnull Map<String, Object> args) {
241 // instantiate the parts
242 Map<String, Object> instanceMap = new LinkedHashMap<>();
243 for (Map.Entry<String, ClassHolder> classEntry : classMap.entrySet()) {
244 String memberType = classEntry.getKey();
245 if (args.containsKey(memberType)) {
246 // use provided value, even if null
247 instanceMap.put(memberType, args.get(memberType));
248 } else {
249 // otherwise create a new value
250 ClassHolder classHolder = classEntry.getValue();
251 if (classHolder.artifactClass != null) {
252 Class<? extends GriffonArtifact> memberClass = classHolder.artifactClass;
253 ArtifactManager artifactManager = getApplication().getArtifactManager();
254 GriffonClass griffonClass = artifactManager.findGriffonClass(memberClass);
255 GriffonArtifact instance = artifactManager.newInstance(griffonClass);
256 instanceMap.put(memberType, instance);
257 args.put(memberType, instance);
258 } else {
259 Class<?> memberClass = classHolder.regularClass;
260 try {
261 Object instance = memberClass.newInstance();
262 getApplication().getInjector().injectMembers(instance);
263 instanceMap.put(memberType, instance);
264 args.put(memberType, instance);
265 } catch (InstantiationException | IllegalAccessException e) {
266 LOG.error("Can't create member {} with {}", memberType, memberClass);
267 throw new NewInstanceException(memberClass, e);
268 }
269 }
270 }
271 }
272 return instanceMap;
273 }
274
275 protected void initializeMembers(@Nonnull MVCGroup group, @Nonnull Map<String, Object> args) {
276 LOG.debug("Initializing each MVC member of group '{}'", group.getMvcId());
277 for (Map.Entry<String, Object> memberEntry : group.getMembers().entrySet()) {
278 String memberType = memberEntry.getKey();
279 Object member = memberEntry.getValue();
280 if (member instanceof GriffonArtifact) {
281 initializeArtifactMember(group, memberType, (GriffonArtifact) member, args);
282 } else {
283 initializeNonArtifactMember(group, memberType, member, args);
284 }
285 }
286 }
287
288 protected void initializeArtifactMember(@Nonnull MVCGroup group, @Nonnull String type, final @Nonnull GriffonArtifact member, final @Nonnull Map<String, Object> args) {
289 if (member instanceof GriffonView) {
290 getApplication().getUIThreadManager().runInsideUISync(new Runnable() {
291 @Override
292 public void run() {
293 try {
294 GriffonView view = (GriffonView) member;
295 view.initUI();
296 view.mvcGroupInit(args);
297 } catch (RuntimeException e) {
298 throw (RuntimeException) sanitize(e);
299 }
300 }
301 });
302 } else if (member instanceof GriffonMvcArtifact) {
303 ((GriffonMvcArtifact) member).mvcGroupInit(args);
304 }
305 }
306
307 protected void initializeNonArtifactMember(@Nonnull MVCGroup group, @Nonnull String type, @Nonnull Object member, @Nonnull Map<String, Object> args) {
308 // empty
309 }
310
311 protected void fillReferencedProperties(@Nonnull MVCGroup group, @Nonnull Map<String, Object> args) {
312 for (Map.Entry<String, Object> memberEntry : group.getMembers().entrySet()) {
313 String memberType = memberEntry.getKey();
314 Object member = memberEntry.getValue();
315 if (member instanceof GriffonArtifact) {
316 fillArtifactMemberProperties(memberType, (GriffonArtifact) member, args);
317 } else {
318 fillNonArtifactMemberProperties(memberType, member, args);
319 }
320 fillContextualMemberProperties(group, memberType, member);
321 }
322 }
323
324 private void fillArtifactMemberProperties(@Nonnull String type, @Nonnull GriffonArtifact member, @Nonnull Map<String, Object> args) {
325 // set the args and instances
326 setPropertiesOrFieldsNoException(member, args);
327 }
328
329 protected void fillNonArtifactMemberProperties(@Nonnull String type, @Nonnull Object member, @Nonnull Map<String, Object> args) {
330 // empty
331 }
332
333 protected void fillContextualMemberProperties(@Nonnull MVCGroup group, @Nonnull String type, @Nonnull Object member) {
334 for (PropertyDescriptor descriptor : getPropertyDescriptors(member.getClass())) {
335 Method method = descriptor.getWriteMethod();
336 if (method != null && method.getAnnotation(Contextual.class) != null) {
337 String key = nameFor(method);
338 Object arg = group.getContext().get(key);
339
340 Nonnull nonNull = findAnnotation(annotationsOfMethodParameter(method, 0), Nonnull.class);
341 if (arg == null && nonNull != null) {
342 throw new IllegalStateException("Could not find an instance of type " +
343 method.getParameterTypes()[0].getName() + " under key '" + key +
344 "' in the context of MVCGroup[" + group.getMvcType() + ":" + group.getMvcId() +
345 "] to be injected on property '" + descriptor.getName() +
346 "' in " + type + " (" + member.getClass().getName() + "). Property does not accept null values.");
347 }
348
349 try {
350 method.invoke(member, arg);
351 } catch (IllegalAccessException | InvocationTargetException e) {
352 throw new MVCGroupInstantiationException(group.getMvcType(), group.getMvcId(), e);
353 }
354 }
355 }
356
357 for (Field field : getAllDeclaredFields(member.getClass())) {
358 if (field.getAnnotation(Contextual.class) != null) {
359 Object value = null;
360 String[] keys = namesFor(field);
361 for (String key : keys) {
362 if (group.getContext().containsKey(key)) {
363 value = group.getContext().get(key);
364 }
365 }
366
367 if (value == null && field.getAnnotation(Nonnull.class) != null) {
368 throw new IllegalStateException("Could not find an instance of type " +
369 field.getType().getName() + " under keys '" + Arrays.toString(keys) +
370 "' in the context of MVCGroup[" + group.getMvcType() + ":" + group.getMvcId() +
371 "] to be injected on field '" + field.getName() +
372 "' in " + type + " (" + member.getClass().getName() + "). Field does not accept null values.");
373 }
374
375 try {
376 setFieldValue(member, field.getName(), value);
377 } catch (FieldException e) {
378 throw new MVCGroupInstantiationException(group.getMvcType(), group.getMvcId(), e);
379 }
380 }
381 }
382 }
383
384 protected void doAddGroup(@Nonnull MVCGroup group) {
385 addGroup(group);
386 }
387
388 public void destroyMVCGroup(@Nonnull String mvcId) {
389 MVCGroup group = findGroup(mvcId);
390 LOG.debug("Group '{}' points to {}", mvcId, group);
391
392 if (group == null) return;
393
394 LOG.debug("Destroying MVC group identified by '{}'", mvcId);
395
396 if (isConfigFlagEnabled(group.getConfiguration(), CONFIG_KEY_EVENTS_LISTENER)) {
397 GriffonController controller = group.getController();
398 if (controller != null) {
399 getApplication().getEventRouter().removeEventListener(controller);
400 }
401 }
402
403 boolean fireDestructionEvents = isConfigFlagEnabled(group.getConfiguration(), CONFIG_KEY_EVENTS_DESTRUCTION);
404
405 destroyMembers(group, fireDestructionEvents);
406
407 doRemoveGroup(group);
408 group.destroy();
409
410 if (isConfigFlagEnabled(group.getConfiguration(), CONFIG_KEY_EVENTS_LIFECYCLE)) {
411 getApplication().getEventRouter().publishEvent(ApplicationEvent.DESTROY_MVC_GROUP.getName(), asList(group));
412 }
413 }
414
415 protected void destroyMembers(@Nonnull MVCGroup group, boolean fireDestructionEvents) {
416 for (Map.Entry<String, Object> memberEntry : group.getMembers().entrySet()) {
417 Object member = memberEntry.getValue();
418 if (member instanceof GriffonArtifact) {
419 destroyArtifactMember(memberEntry.getKey(), (GriffonArtifact) member, fireDestructionEvents);
420 } else {
421 destroyNonArtifactMember(memberEntry.getKey(), member, fireDestructionEvents);
422 }
423
424 }
425
426 if (group instanceof AbstractMVCGroup) {
427 List<Object> injectedInstances = ((AbstractMVCGroup) group).getInjectedInstances();
428 for (Object instance : injectedInstances) {
429 getApplication().getInjector().release(instance);
430 }
431 injectedInstances.clear();
432 }
433 }
434
435 protected void destroyArtifactMember(@Nonnull String type, @Nonnull GriffonArtifact member, boolean fireDestructionEvents) {
436 if (member instanceof GriffonMvcArtifact) {
437 final GriffonMvcArtifact artifact = (GriffonMvcArtifact) member;
438 if (fireDestructionEvents) {
439 getApplication().getEventRouter().publishEvent(ApplicationEvent.DESTROY_INSTANCE.getName(), asList(member.getClass(), artifact));
440 }
441
442 if (artifact instanceof GriffonView) {
443 getApplication().getUIThreadManager().runInsideUISync(new Runnable() {
444 @Override
445 public void run() {
446 try {
447 artifact.mvcGroupDestroy();
448 } catch (RuntimeException e) {
449 throw (RuntimeException) sanitize(e);
450 }
451 }
452 });
453 } else {
454 artifact.mvcGroupDestroy();
455 }
456
457 // clear all parent* references
458 for (String parentMemberName : new String[]{"parentModel", "parentView", "parentController", "parentGroup"}) {
459 setPropertyOrFieldValueNoException(member, parentMemberName, null);
460 }
461 }
462
463 destroyContextualMemberProperties(type, member);
464 }
465
466 protected void destroyContextualMemberProperties(@Nonnull String type, @Nonnull GriffonArtifact member) {
467 for (Field field : getAllDeclaredFields(member.getClass())) {
468 if (field.getAnnotation(Contextual.class) != null) {
469 try {
470 setFieldValue(member, field.getName(), null);
471 } catch (FieldException e) {
472 throw new IllegalStateException("Could not nullify field " +
473 field.getName() + "' in " + type + " (" + member.getClass().getName() + ")", e);
474 }
475 }
476 }
477 }
478
479 protected void destroyNonArtifactMember(@Nonnull String type, @Nonnull Object member, boolean fireDestructionEvents) {
480 // empty
481 }
482
483 protected void doRemoveGroup(@Nonnull MVCGroup group) {
484 removeGroup(group);
485 }
486
487 protected boolean isConfigFlagEnabled(@Nonnull MVCGroupConfiguration configuration, @Nonnull String key) {
488 return getConfigValueAsBoolean(configuration.getConfig(), key, true);
489 }
490
491 @Nullable
492 protected Class<?> loadClass(@Nonnull String className) {
493 try {
494 return applicationClassLoader.get().loadClass(className);
495 } catch (ClassNotFoundException e) {
496 // #39 do not ignore this CNFE
497 throw new GriffonException(e.toString(), e);
498 }
499 }
500
501 protected static final class ClassHolder {
502 protected Class<?> regularClass;
503 protected Class<? extends GriffonArtifact> artifactClass;
504 }
505 }
|