DefaultMVCGroupManager.java
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, (MVCGroupargs.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_GROUPinstanceof MVCGroup) {
201                 MVCGroup parentGroup = (MVCGroupargs.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, (GriffonArtifactmember, 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 = (GriffonViewmember;
280                         view.initUI();
281                         view.mvcGroupInit(args);
282                     catch (RuntimeException e) {
283                         throw (RuntimeExceptionsanitize(e);
284                     }
285                 }
286             });
287         else if (member instanceof GriffonMvcArtifact) {
288             ((GriffonMvcArtifactmember).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, (GriffonArtifactmember, 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 == nullreturn;
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()(GriffonArtifactmemberEntry.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 = (GriffonMvcArtifactmember;
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 }