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