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