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