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