001/** 002 * 003 * Copyright 2019-2021 Florian Schmaus 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smack.c2s; 018 019import java.io.PrintWriter; 020import java.io.StringWriter; 021import java.lang.reflect.Constructor; 022import java.lang.reflect.InvocationTargetException; 023import java.util.Arrays; 024import java.util.Collections; 025import java.util.HashMap; 026import java.util.HashSet; 027import java.util.Map; 028import java.util.Set; 029 030import org.jivesoftware.smack.ConnectionConfiguration; 031import org.jivesoftware.smack.SmackConfiguration; 032import org.jivesoftware.smack.fsm.StateDescriptor; 033import org.jivesoftware.smack.fsm.StateDescriptorGraph; 034import org.jivesoftware.smack.fsm.StateDescriptorGraph.GraphVertex; 035import org.jivesoftware.smack.util.CollectionUtil; 036 037public final class ModularXmppClientToServerConnectionConfiguration extends ConnectionConfiguration { 038 039 final Set<ModularXmppClientToServerConnectionModuleDescriptor> moduleDescriptors; 040 041 final GraphVertex<StateDescriptor> initialStateDescriptorVertex; 042 043 private ModularXmppClientToServerConnectionConfiguration(Builder builder) { 044 super(builder); 045 046 moduleDescriptors = Collections.unmodifiableSet(CollectionUtil.newSetWith(builder.modulesDescriptors.values())); 047 048 Set<Class<? extends StateDescriptor>> backwardEdgeStateDescriptors = new HashSet<>(); 049 // Add backward edges from configured connection modules. Note that all state descriptors from module 050 // descriptors are backwards edges. 051 for (ModularXmppClientToServerConnectionModuleDescriptor moduleDescriptor : moduleDescriptors) { 052 Set<Class<? extends StateDescriptor>> moduleStateDescriptors = moduleDescriptor.getStateDescriptors(); 053 backwardEdgeStateDescriptors.addAll(moduleStateDescriptors); 054 } 055 056 try { 057 initialStateDescriptorVertex = StateDescriptorGraph.constructStateDescriptorGraph(backwardEdgeStateDescriptors, builder.failOnUnknownStates); 058 } 059 catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException 060 | NoSuchMethodException | SecurityException e) { 061 // TODO: Depending on the exact exception thrown, this potentially indicates an invalid connection 062 // configuration, e.g. there is no edge from disconnected to connected. 063 throw new IllegalStateException(e); 064 } 065 066 for (ModularXmppClientToServerConnectionModuleDescriptor moduleDescriptor : moduleDescriptors) { 067 moduleDescriptor.validateConfiguration(this); 068 } 069 } 070 071 public void printStateGraphInDotFormat(PrintWriter pw, boolean breakStateName) { 072 StateDescriptorGraph.stateDescriptorGraphToDot(Collections.singleton(initialStateDescriptorVertex), pw, 073 breakStateName); 074 } 075 076 public String getStateGraphInDotFormat() { 077 StringWriter sw = new StringWriter(); 078 PrintWriter pw = new PrintWriter(sw); 079 080 printStateGraphInDotFormat(pw, true); 081 082 return sw.toString(); 083 } 084 085 public static Builder builder() { 086 return new Builder(); 087 } 088 089 public static final class Builder 090 extends ConnectionConfiguration.Builder<Builder, ModularXmppClientToServerConnectionConfiguration> { 091 092 private final Map<Class<? extends ModularXmppClientToServerConnectionModuleDescriptor>, ModularXmppClientToServerConnectionModuleDescriptor> modulesDescriptors = new HashMap<>(); 093 094 private boolean failOnUnknownStates; 095 096 private Builder() { 097 SmackConfiguration.addAllKnownModulesTo(this); 098 } 099 100 @Override 101 public ModularXmppClientToServerConnectionConfiguration build() { 102 return new ModularXmppClientToServerConnectionConfiguration(this); 103 } 104 105 public void addModule(ModularXmppClientToServerConnectionModuleDescriptor connectionModule) { 106 Class<? extends ModularXmppClientToServerConnectionModuleDescriptor> moduleDescriptorClass = connectionModule.getClass(); 107 if (modulesDescriptors.containsKey(moduleDescriptorClass)) { 108 throw new IllegalArgumentException("A connection module for " + moduleDescriptorClass + " is already configured"); 109 } 110 modulesDescriptors.put(moduleDescriptorClass, connectionModule); 111 } 112 113 @SuppressWarnings("unchecked") 114 public Builder addModule(Class<? extends ModularXmppClientToServerConnectionModuleDescriptor> moduleClass) { 115 Class<?>[] declaredClasses = moduleClass.getDeclaredClasses(); 116 117 Class<? extends ModularXmppClientToServerConnectionModuleDescriptor.Builder> builderClass = null; 118 for (Class<?> declaredClass : declaredClasses) { 119 if (!ModularXmppClientToServerConnectionModuleDescriptor.Builder.class.isAssignableFrom(declaredClass)) { 120 continue; 121 } 122 123 builderClass = (Class<? extends ModularXmppClientToServerConnectionModuleDescriptor.Builder>) declaredClass; 124 break; 125 } 126 127 if (builderClass == null) { 128 throw new IllegalArgumentException( 129 "Found no builder for " + moduleClass + ". Delcared classes: " + Arrays.toString(declaredClasses)); 130 } 131 132 return with(builderClass).buildModule(); 133 } 134 135 public <B extends ModularXmppClientToServerConnectionModuleDescriptor.Builder> B with( 136 Class<? extends B> moduleDescriptorBuilderClass) { 137 Constructor<? extends B> moduleDescriptorBuilderCosntructor; 138 try { 139 moduleDescriptorBuilderCosntructor = moduleDescriptorBuilderClass.getDeclaredConstructor( 140 ModularXmppClientToServerConnectionConfiguration.Builder.class); 141 } catch (NoSuchMethodException | SecurityException e) { 142 throw new IllegalArgumentException(e); 143 } 144 145 moduleDescriptorBuilderCosntructor.setAccessible(true); 146 147 B moduleDescriptorBuilder; 148 try { 149 moduleDescriptorBuilder = moduleDescriptorBuilderCosntructor.newInstance(this); 150 } catch (InstantiationException | IllegalAccessException | IllegalArgumentException 151 | InvocationTargetException e) { 152 throw new IllegalArgumentException(e); 153 } 154 155 return moduleDescriptorBuilder; 156 } 157 158 public Builder removeModule(Class<? extends ModularXmppClientToServerConnectionModuleDescriptor> moduleClass) { 159 modulesDescriptors.remove(moduleClass); 160 return getThis(); 161 } 162 163 public Builder removeAllModules() { 164 modulesDescriptors.clear(); 165 return getThis(); 166 } 167 168 /** 169 * Fail if there are unknown states in Smack's state descriptor graph. This method is used mostly for testing 170 * the internals of Smack. Users can safely ignore it. 171 * 172 * @return a reference to this builder. 173 */ 174 public Builder failOnUnknownStates() { 175 failOnUnknownStates = true; 176 return getThis(); 177 } 178 179 @Override 180 protected Builder getThis() { 181 return this; 182 } 183 } 184}