|
| 1 | +/* |
| 2 | + * Licensed to the Apache Software Foundation (ASF) under one |
| 3 | + * or more contributor license agreements. See the NOTICE file |
| 4 | + * distributed with this work for additional information |
| 5 | + * regarding copyright ownership. The ASF licenses this file |
| 6 | + * to you under the Apache License, Version 2.0 (the |
| 7 | + * "License"); you may not use this file except in compliance |
| 8 | + * with the License. You may obtain a copy of the License at |
| 9 | + * |
| 10 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | + * |
| 12 | + * Unless required by applicable law or agreed to in writing, software |
| 13 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 14 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 15 | + * See the License for the specific language governing permissions and |
| 16 | + * limitations under the License. |
| 17 | + */ |
| 18 | +package org.apache.beam.sdk.io.jms; |
| 19 | + |
| 20 | +import static org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Preconditions.checkArgument; |
| 21 | + |
| 22 | +import com.google.auto.value.AutoValue; |
| 23 | +import com.ibm.mq.jms.MQConnectionFactory; |
| 24 | +import com.ibm.msg.client.wmq.WMQConstants; |
| 25 | +import java.io.Serializable; |
| 26 | +import java.lang.reflect.InvocationTargetException; |
| 27 | +import java.lang.reflect.Method; |
| 28 | +import java.net.URI; |
| 29 | +import java.util.List; |
| 30 | +import javax.jms.ConnectionFactory; |
| 31 | +import org.apache.beam.sdk.schemas.AutoValueSchema; |
| 32 | +import org.apache.beam.sdk.schemas.annotations.DefaultSchema; |
| 33 | +import org.apache.beam.sdk.schemas.annotations.SchemaFieldDescription; |
| 34 | +import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Splitter; |
| 35 | +import org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Strings; |
| 36 | +import org.checkerframework.checker.nullness.qual.Nullable; |
| 37 | +import org.slf4j.Logger; |
| 38 | +import org.slf4j.LoggerFactory; |
| 39 | + |
| 40 | +/** A POJO describing a JMS connection, used by SchemaTransformProvider. */ |
| 41 | +@DefaultSchema(AutoValueSchema.class) |
| 42 | +@AutoValue |
| 43 | +public abstract class ConnectionConfiguration implements Serializable { |
| 44 | + private static final Logger LOG = LoggerFactory.getLogger(ConnectionConfiguration.class); |
| 45 | + |
| 46 | + public static Builder builder() { |
| 47 | + return new AutoValue_ConnectionConfiguration.Builder(); |
| 48 | + } |
| 49 | + |
| 50 | + public static ConnectionConfiguration create( |
| 51 | + String serverUri, @Nullable String connectionFactoryClassName) { |
| 52 | + checkArgument(serverUri != null, "serverUri can not be null"); |
| 53 | + return builder() |
| 54 | + .setServerUri(serverUri) |
| 55 | + .setConnectionFactoryClassName(connectionFactoryClassName) |
| 56 | + .build(); |
| 57 | + } |
| 58 | + |
| 59 | + public static ConnectionConfiguration create(String serverUri) { |
| 60 | + return create(serverUri, null); |
| 61 | + } |
| 62 | + |
| 63 | + @SchemaFieldDescription("The JMS broker URI.") |
| 64 | + public abstract String getServerUri(); |
| 65 | + |
| 66 | + @SchemaFieldDescription("The JMS ConnectionFactory class name.") |
| 67 | + public abstract @Nullable String getConnectionFactoryClassName(); |
| 68 | + |
| 69 | + @SchemaFieldDescription("The username to connect to the JMS broker.") |
| 70 | + public abstract @Nullable String getUsername(); |
| 71 | + |
| 72 | + @SchemaFieldDescription("The password to connect to the JMS broker.") |
| 73 | + public abstract @Nullable String getPassword(); |
| 74 | + |
| 75 | + public ConnectionConfiguration withUsername(String username) { |
| 76 | + return toBuilder().setUsername(username).build(); |
| 77 | + } |
| 78 | + |
| 79 | + public ConnectionConfiguration withPassword(String password) { |
| 80 | + return toBuilder().setPassword(password).build(); |
| 81 | + } |
| 82 | + |
| 83 | + public ConnectionConfiguration withConnectionFactoryClassName(String connectionFactoryClassName) { |
| 84 | + return toBuilder().setConnectionFactoryClassName(connectionFactoryClassName).build(); |
| 85 | + } |
| 86 | + |
| 87 | + abstract Builder toBuilder(); |
| 88 | + |
| 89 | + @AutoValue.Builder |
| 90 | + public abstract static class Builder { |
| 91 | + public abstract Builder setServerUri(String serverUri); |
| 92 | + |
| 93 | + public abstract Builder setConnectionFactoryClassName( |
| 94 | + @Nullable String connectionFactoryClassName); |
| 95 | + |
| 96 | + public abstract Builder setUsername(@Nullable String username); |
| 97 | + |
| 98 | + public abstract Builder setPassword(@Nullable String password); |
| 99 | + |
| 100 | + public abstract ConnectionConfiguration build(); |
| 101 | + } |
| 102 | + |
| 103 | + public ConnectionFactory createConnectionFactory() { |
| 104 | + String className = getConnectionFactoryClassName(); |
| 105 | + // Default to ActiveMQ |
| 106 | + if (className == null || className.isEmpty()) { |
| 107 | + className = "org.apache.activemq.ActiveMQConnectionFactory"; |
| 108 | + } |
| 109 | + Class<?> clazz; |
| 110 | + Class<? extends BeamGenericJmsConnectionFactory> factoryClass; |
| 111 | + try { |
| 112 | + clazz = Class.forName(className); |
| 113 | + } catch (ClassNotFoundException e) { |
| 114 | + throw new IllegalArgumentException( |
| 115 | + String.format( |
| 116 | + "ConnectionFactory %s does not exist. If using expansion service, attach the connection factory jar as part of its invocation classpath.", |
| 117 | + className), |
| 118 | + e); |
| 119 | + } |
| 120 | + if (BeamGenericJmsConnectionFactory.class.isAssignableFrom(clazz)) { |
| 121 | + factoryClass = (Class<? extends BeamGenericJmsConnectionFactory>) clazz; |
| 122 | + } else if (className.contains("org.apache.activemq.ActiveMQConnectionFactory") |
| 123 | + || className.contains("org.apache.qpid.jms")) { |
| 124 | + // Connectors supported by StandardJmsConnectionFactory |
| 125 | + factoryClass = StandardJmsConnectionFactory.class; |
| 126 | + } else if (className.contains("com.ibm.mq")) { |
| 127 | + factoryClass = IbmMqJmsConnectionFactory.class; |
| 128 | + } else { |
| 129 | + // Attempt to use StandardJmsConnectionFactory.class; |
| 130 | + factoryClass = StandardJmsConnectionFactory.class; |
| 131 | + } |
| 132 | + try { |
| 133 | + BeamGenericJmsConnectionFactory factory = factoryClass.getDeclaredConstructor().newInstance(); |
| 134 | + return factory.createConnectionFactory(this); |
| 135 | + } catch (Exception e) { |
| 136 | + throw new IllegalArgumentException( |
| 137 | + "Unable to instantiate JMS ConnectionFactory of class " |
| 138 | + + className |
| 139 | + + ". Must be a supported provider (ActiveMQ, Qpid, IBM MQ) or implement BeamGenericJmsConnectionFactory.", |
| 140 | + e); |
| 141 | + } |
| 142 | + } |
| 143 | + |
| 144 | + /** |
| 145 | + * A {@link BeamGenericJmsConnectionFactory} implementation for standard JMS connection factories. |
| 146 | + */ |
| 147 | + public static class StandardJmsConnectionFactory implements BeamGenericJmsConnectionFactory { |
| 148 | + |
| 149 | + @Override |
| 150 | + public ConnectionFactory createConnectionFactory(ConnectionConfiguration config) |
| 151 | + throws Exception { |
| 152 | + String className = config.getConnectionFactoryClassName(); |
| 153 | + if (className == null || className.isEmpty()) { |
| 154 | + className = "org.apache.activemq.ActiveMQConnectionFactory"; |
| 155 | + } |
| 156 | + Class<?> clazz = Class.forName(className); |
| 157 | + String uri = config.getServerUri(); |
| 158 | + String username = config.getUsername(); |
| 159 | + String password = config.getPassword(); |
| 160 | + |
| 161 | + if (username != null && password != null) { |
| 162 | + try { |
| 163 | + return (ConnectionFactory) |
| 164 | + clazz |
| 165 | + .getConstructor(String.class, String.class, String.class) |
| 166 | + .newInstance(username, password, uri); |
| 167 | + } catch (NoSuchMethodException e) { |
| 168 | + // Fall through to 1-arg or 0-arg constructor + setters |
| 169 | + } |
| 170 | + } |
| 171 | + ConnectionFactory cf; |
| 172 | + try { |
| 173 | + cf = (ConnectionFactory) clazz.getConstructor(String.class).newInstance(uri); |
| 174 | + } catch (NoSuchMethodException e) { |
| 175 | + cf = (ConnectionFactory) clazz.getConstructor().newInstance(); |
| 176 | + } |
| 177 | + |
| 178 | + if (username != null && password != null) { |
| 179 | + boolean setUsernameSuccess = |
| 180 | + // ActiveMQ (capital N) |
| 181 | + invokeMethodIfExists(cf, "setUserName", String.class, username) |
| 182 | + // Qpid (lowercase n) |
| 183 | + || invokeMethodIfExists(cf, "setUsername", String.class, username); |
| 184 | + boolean setPasswordSuccess = |
| 185 | + invokeMethodIfExists(cf, "setPassword", String.class, password); |
| 186 | + |
| 187 | + if (!setUsernameSuccess || !setPasswordSuccess) { |
| 188 | + LOG.warn("Unable to set username/password on JMS ConnectionFactory of class {}", clazz); |
| 189 | + } |
| 190 | + } |
| 191 | + return cf; |
| 192 | + } |
| 193 | + |
| 194 | + private static boolean invokeMethodIfExists( |
| 195 | + Object target, String methodName, Class<?> paramType, Object arg) { |
| 196 | + try { |
| 197 | + Method m = target.getClass().getMethod(methodName, paramType); |
| 198 | + m.invoke(target, arg); |
| 199 | + return true; |
| 200 | + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { |
| 201 | + return false; |
| 202 | + } |
| 203 | + } |
| 204 | + } |
| 205 | + |
| 206 | + /** A {@link BeamGenericJmsConnectionFactory} implementation for IBM MQ. */ |
| 207 | + public static class IbmMqJmsConnectionFactory implements BeamGenericJmsConnectionFactory { |
| 208 | + |
| 209 | + @Override |
| 210 | + public ConnectionFactory createConnectionFactory(ConnectionConfiguration config) |
| 211 | + throws Exception { |
| 212 | + MQConnectionFactory cf = new MQConnectionFactory(); |
| 213 | + cf.setTransportType(WMQConstants.WMQ_CM_CLIENT); |
| 214 | + |
| 215 | + String uri = config.getServerUri(); |
| 216 | + if (!Strings.isNullOrEmpty(uri)) { |
| 217 | + URI parsedUri = new URI(uri); |
| 218 | + String host = parsedUri.getHost(); |
| 219 | + int port = parsedUri.getPort(); |
| 220 | + if (host != null) { |
| 221 | + cf.setHostName(host); |
| 222 | + } |
| 223 | + if (port > 0) { |
| 224 | + cf.setPort(port); |
| 225 | + } |
| 226 | + if (parsedUri.getQuery() != null) { |
| 227 | + for (String param : Splitter.on('&').split(parsedUri.getQuery())) { |
| 228 | + List<String> pair = Splitter.on('=').splitToList(param); |
| 229 | + if (pair.size() == 2) { |
| 230 | + if ("channel".equalsIgnoreCase(pair.get(0))) { |
| 231 | + cf.setChannel(pair.get(1)); |
| 232 | + } else if ("queueManager".equalsIgnoreCase(pair.get(0))) { |
| 233 | + cf.setQueueManager(pair.get(1)); |
| 234 | + } |
| 235 | + } |
| 236 | + } |
| 237 | + } |
| 238 | + } |
| 239 | + |
| 240 | + String username = config.getUsername(); |
| 241 | + if (username != null) { |
| 242 | + cf.setBooleanProperty(WMQConstants.USER_AUTHENTICATION_MQCSP, true); |
| 243 | + cf.setStringProperty(WMQConstants.USERID, username); |
| 244 | + String password = config.getPassword(); |
| 245 | + if (password != null) { |
| 246 | + cf.setStringProperty(WMQConstants.PASSWORD, password); |
| 247 | + } |
| 248 | + } |
| 249 | + return cf; |
| 250 | + } |
| 251 | + } |
| 252 | +} |
0 commit comments