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