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