ModularXmppClientToServerConnectionConfiguration.java

  1. /**
  2.  *
  3.  * Copyright 2019-2021 Florian Schmaus
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smack.c2s;

  18. import java.io.PrintWriter;
  19. import java.io.StringWriter;
  20. import java.lang.reflect.Constructor;
  21. import java.lang.reflect.InvocationTargetException;
  22. import java.util.Arrays;
  23. import java.util.Collections;
  24. import java.util.HashMap;
  25. import java.util.HashSet;
  26. import java.util.Map;
  27. import java.util.Set;

  28. import org.jivesoftware.smack.ConnectionConfiguration;
  29. import org.jivesoftware.smack.SmackConfiguration;
  30. import org.jivesoftware.smack.fsm.StateDescriptor;
  31. import org.jivesoftware.smack.fsm.StateDescriptorGraph;
  32. import org.jivesoftware.smack.fsm.StateDescriptorGraph.GraphVertex;
  33. import org.jivesoftware.smack.util.CollectionUtil;

  34. public final class ModularXmppClientToServerConnectionConfiguration extends ConnectionConfiguration {

  35.     final Set<ModularXmppClientToServerConnectionModuleDescriptor> moduleDescriptors;

  36.     final GraphVertex<StateDescriptor> initialStateDescriptorVertex;

  37.     private ModularXmppClientToServerConnectionConfiguration(Builder builder) {
  38.         super(builder);

  39.         moduleDescriptors = Collections.unmodifiableSet(CollectionUtil.newSetWith(builder.modulesDescriptors.values()));

  40.         Set<Class<? extends StateDescriptor>> backwardEdgeStateDescriptors = new HashSet<>();
  41.         // Add backward edges from configured connection modules. Note that all state descriptors from module
  42.         // descriptors are backwards edges.
  43.         for (ModularXmppClientToServerConnectionModuleDescriptor moduleDescriptor : moduleDescriptors) {
  44.             Set<Class<? extends StateDescriptor>> moduleStateDescriptors = moduleDescriptor.getStateDescriptors();
  45.             backwardEdgeStateDescriptors.addAll(moduleStateDescriptors);
  46.         }

  47.         try {
  48.             initialStateDescriptorVertex = StateDescriptorGraph.constructStateDescriptorGraph(backwardEdgeStateDescriptors, builder.failOnUnknownStates);
  49.         }
  50.         catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
  51.                         | NoSuchMethodException | SecurityException e) {
  52.             // TODO: Depending on the exact exception thrown, this potentially indicates an invalid connection
  53.             // configuration, e.g. there is no edge from disconnected to connected.
  54.             throw new IllegalStateException(e);
  55.         }

  56.         for (ModularXmppClientToServerConnectionModuleDescriptor moduleDescriptor : moduleDescriptors) {
  57.             moduleDescriptor.validateConfiguration(this);
  58.         }
  59.     }

  60.     public void printStateGraphInDotFormat(PrintWriter pw, boolean breakStateName) {
  61.         StateDescriptorGraph.stateDescriptorGraphToDot(Collections.singleton(initialStateDescriptorVertex), pw,
  62.                         breakStateName);
  63.     }

  64.     public String getStateGraphInDotFormat() {
  65.         StringWriter sw = new StringWriter();
  66.         PrintWriter pw = new PrintWriter(sw);

  67.         printStateGraphInDotFormat(pw, true);

  68.         return sw.toString();
  69.     }

  70.     public static Builder builder() {
  71.         return new Builder();
  72.     }

  73.     public static final class Builder
  74.                     extends ConnectionConfiguration.Builder<Builder, ModularXmppClientToServerConnectionConfiguration> {

  75.         private final Map<Class<? extends ModularXmppClientToServerConnectionModuleDescriptor>, ModularXmppClientToServerConnectionModuleDescriptor> modulesDescriptors = new HashMap<>();

  76.         private boolean failOnUnknownStates;

  77.         private Builder() {
  78.             SmackConfiguration.addAllKnownModulesTo(this);
  79.         }

  80.         @Override
  81.         public ModularXmppClientToServerConnectionConfiguration build() {
  82.             return new ModularXmppClientToServerConnectionConfiguration(this);
  83.         }

  84.         public void addModule(ModularXmppClientToServerConnectionModuleDescriptor connectionModule) {
  85.             Class<? extends ModularXmppClientToServerConnectionModuleDescriptor> moduleDescriptorClass = connectionModule.getClass();
  86.             if (modulesDescriptors.containsKey(moduleDescriptorClass)) {
  87.                 throw new IllegalArgumentException("A connection module for " + moduleDescriptorClass + " is already configured");
  88.             }
  89.             modulesDescriptors.put(moduleDescriptorClass, connectionModule);
  90.         }

  91.         @SuppressWarnings("unchecked")
  92.         public Builder addModule(Class<? extends ModularXmppClientToServerConnectionModuleDescriptor> moduleClass) {
  93.             Class<?>[] declaredClasses = moduleClass.getDeclaredClasses();

  94.             Class<? extends ModularXmppClientToServerConnectionModuleDescriptor.Builder> builderClass = null;
  95.             for (Class<?> declaredClass : declaredClasses) {
  96.                 if (!ModularXmppClientToServerConnectionModuleDescriptor.Builder.class.isAssignableFrom(declaredClass)) {
  97.                     continue;
  98.                 }

  99.                 builderClass = (Class<? extends ModularXmppClientToServerConnectionModuleDescriptor.Builder>) declaredClass;
  100.                 break;
  101.             }

  102.             if (builderClass == null) {
  103.                 throw new IllegalArgumentException(
  104.                                 "Found no builder for " + moduleClass + ". Delcared classes: " + Arrays.toString(declaredClasses));
  105.             }

  106.             return with(builderClass).buildModule();
  107.         }

  108.         public <B extends ModularXmppClientToServerConnectionModuleDescriptor.Builder> B with(
  109.                         Class<? extends B> moduleDescriptorBuilderClass) {
  110.             Constructor<? extends B> moduleDescriptorBuilderCosntructor;
  111.             try {
  112.                 moduleDescriptorBuilderCosntructor = moduleDescriptorBuilderClass.getDeclaredConstructor(
  113.                                 ModularXmppClientToServerConnectionConfiguration.Builder.class);
  114.             } catch (NoSuchMethodException | SecurityException e) {
  115.                 throw new IllegalArgumentException(e);
  116.             }

  117.             moduleDescriptorBuilderCosntructor.setAccessible(true);

  118.             B moduleDescriptorBuilder;
  119.             try {
  120.                 moduleDescriptorBuilder = moduleDescriptorBuilderCosntructor.newInstance(this);
  121.             } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
  122.                             | InvocationTargetException e) {
  123.                 throw new IllegalArgumentException(e);
  124.             }

  125.             return moduleDescriptorBuilder;
  126.         }

  127.         public Builder removeModule(Class<? extends ModularXmppClientToServerConnectionModuleDescriptor> moduleClass) {
  128.             modulesDescriptors.remove(moduleClass);
  129.             return getThis();
  130.         }

  131.         public Builder removeAllModules() {
  132.             modulesDescriptors.clear();
  133.             return getThis();
  134.         }

  135.         /**
  136.          * Fail if there are unknown states in Smack's state descriptor graph. This method is used mostly for testing
  137.          * the internals of Smack. Users can safely ignore it.
  138.          *
  139.          * @return a reference to this builder.
  140.          */
  141.         public Builder failOnUnknownStates() {
  142.             failOnUnknownStates = true;
  143.             return getThis();
  144.         }

  145.         @Override
  146.         protected Builder getThis() {
  147.             return this;
  148.         }
  149.     }
  150. }