| 
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                         instanceMap.put(memberType, instance);
 248                         args.put(memberType, instance);
 249                     } catch (InstantiationException | IllegalAccessException e) {
 250                         LOG.error("Can't create member {} with {}", memberType, memberClass);
 251                         throw new NewInstanceException(memberClass, e);
 252                     }
 253                 }
 254             }
 255         }
 256         return instanceMap;
 257     }
 258
 259     protected void initializeMembers(@Nonnull MVCGroup group, @Nonnull Map<String, Object> args) {
 260         LOG.debug("Initializing each MVC member of group '{}'", group.getMvcId());
 261         for (Map.Entry<String, Object> memberEntry : group.getMembers().entrySet()) {
 262             String memberType = memberEntry.getKey();
 263             Object member = memberEntry.getValue();
 264             if (member instanceof GriffonArtifact) {
 265                 initializeArtifactMember(group, memberType, (GriffonArtifact) member, args);
 266             } else {
 267                 initializeNonArtifactMember(group, memberType, member, args);
 268             }
 269         }
 270     }
 271
 272     protected void initializeArtifactMember(@Nonnull MVCGroup group, @Nonnull String type, final @Nonnull GriffonArtifact member, final @Nonnull Map<String, Object> args) {
 273         if (member instanceof GriffonView) {
 274             getApplication().getUIThreadManager().runInsideUISync(new Runnable() {
 275                 @Override
 276                 public void run() {
 277                     try {
 278                         GriffonView view = (GriffonView) member;
 279                         view.initUI();
 280                         view.mvcGroupInit(args);
 281                     } catch (RuntimeException e) {
 282                         throw (RuntimeException) sanitize(e);
 283                     }
 284                 }
 285             });
 286         } else if (member instanceof GriffonMvcArtifact) {
 287             ((GriffonMvcArtifact) member).mvcGroupInit(args);
 288         }
 289     }
 290
 291     protected void initializeNonArtifactMember(@Nonnull MVCGroup group, @Nonnull String type, @Nonnull Object member, @Nonnull Map<String, Object> args) {
 292         // empty
 293     }
 294
 295     protected void fillReferencedProperties(@Nonnull MVCGroup group, @Nonnull Map<String, Object> args) {
 296         for (Map.Entry<String, Object> memberEntry : group.getMembers().entrySet()) {
 297             String memberType = memberEntry.getKey();
 298             Object member = memberEntry.getValue();
 299             if (member instanceof GriffonArtifact) {
 300                 fillArtifactMemberProperties(memberType, (GriffonArtifact) member, args);
 301             } else {
 302                 fillNonArtifactMemberProperties(memberType, member, args);
 303             }
 304         }
 305     }
 306
 307     private void fillArtifactMemberProperties(@Nonnull String type, @Nonnull GriffonArtifact member, @Nonnull Map<String, Object> args) {
 308         // set the args and instances
 309         setPropertiesOrFieldsNoException(member, args);
 310     }
 311
 312     protected void fillNonArtifactMemberProperties(@Nonnull String type, @Nonnull Object member, @Nonnull Map<String, Object> args) {
 313         // empty
 314     }
 315
 316     protected void doAddGroup(@Nonnull MVCGroup group) {
 317         addGroup(group);
 318     }
 319
 320     public void destroyMVCGroup(@Nonnull String mvcId) {
 321         MVCGroup group = findGroup(mvcId);
 322         LOG.debug("Group '{}' points to {}", mvcId, group);
 323
 324         if (group == null) return;
 325
 326         LOG.debug("Destroying MVC group identified by '{}'", mvcId);
 327
 328         if (isConfigFlagEnabled(group.getConfiguration(), CONFIG_KEY_EVENTS_LISTENER)) {
 329             GriffonController controller = group.getController();
 330             if (controller != null) {
 331                 getApplication().getEventRouter().removeEventListener(controller);
 332             }
 333         }
 334
 335         boolean fireDestructionEvents = isConfigFlagEnabled(group.getConfiguration(), CONFIG_KEY_EVENTS_DESTRUCTION);
 336
 337         destroyMembers(group, fireDestructionEvents);
 338
 339         doRemoveGroup(group);
 340         group.destroy();
 341
 342         if (isConfigFlagEnabled(group.getConfiguration(), CONFIG_KEY_EVENTS_LIFECYCLE)) {
 343             getApplication().getEventRouter().publishEvent(ApplicationEvent.DESTROY_MVC_GROUP.getName(), asList(group));
 344         }
 345     }
 346
 347     protected void destroyMembers(@Nonnull MVCGroup group, boolean fireDestructionEvents) {
 348         for (Map.Entry<String, Object> memberEntry : group.getMembers().entrySet()) {
 349             if (memberEntry.getValue() instanceof GriffonArtifact) {
 350                 destroyArtifactMember(memberEntry.getKey(), (GriffonArtifact) memberEntry.getValue(), fireDestructionEvents);
 351             } else {
 352                 destroyNonArtifactMember(memberEntry.getKey(), memberEntry.getValue(), fireDestructionEvents);
 353             }
 354         }
 355     }
 356
 357     protected void destroyArtifactMember(@Nonnull String type, @Nonnull GriffonArtifact member, boolean fireDestructionEvents) {
 358         if (member instanceof GriffonMvcArtifact) {
 359             GriffonMvcArtifact artifact = (GriffonMvcArtifact) member;
 360             if (fireDestructionEvents) {
 361                 getApplication().getEventRouter().publishEvent(ApplicationEvent.DESTROY_INSTANCE.getName(), asList(member.getClass(), artifact));
 362             }
 363             artifact.mvcGroupDestroy();
 364         }
 365     }
 366
 367     protected void destroyNonArtifactMember(@Nonnull String type, @Nonnull Object member, boolean fireDestructionEvents) {
 368         // empty
 369     }
 370
 371     protected void doRemoveGroup(@Nonnull MVCGroup group) {
 372         removeGroup(group);
 373     }
 374
 375     protected boolean isConfigFlagEnabled(@Nonnull MVCGroupConfiguration configuration, @Nonnull String key) {
 376         return getConfigValueAsBoolean(configuration.getConfig(), key, true);
 377     }
 378
 379     @Nullable
 380     protected Class<?> loadClass(@Nonnull String className) {
 381         try {
 382             return applicationClassLoader.get().loadClass(className);
 383         } catch (ClassNotFoundException e) {
 384             // #39 do not ignore this CNFE
 385             throw new GriffonException(e.toString(), e);
 386         }
 387     }
 388
 389     protected static final class ClassHolder {
 390         protected Class<?> regularClass;
 391         protected Class<? extends GriffonArtifact> artifactClass;
 392     }
 393 }
 |