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, (GriffonArtifact) member, 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 = (GriffonView) member;
252 view.initUI();
253 view.mvcGroupInit(args);
254 } catch (RuntimeException e) {
255 throw (RuntimeException) sanitize(e);
256 }
257 }
258 });
259 } else if (member instanceof GriffonMvcArtifact) {
260 ((GriffonMvcArtifact) member).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, (GriffonArtifact) member, 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 == null) return;
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(), (GriffonArtifact) memberEntry.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 = (GriffonMvcArtifact) member;
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 }
|