1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 2002, 2013 Oracle and/or its affiliates.  All rights reserved.
5  *
6  */
7 
8 package com.sleepycat.je.rep.utilint.net;
9 
10 import java.lang.reflect.Constructor;
11 import java.lang.reflect.InvocationTargetException;
12 import java.util.concurrent.atomic.AtomicInteger;
13 import java.util.logging.Formatter;
14 import java.util.logging.Level;
15 import java.util.logging.Logger;
16 
17 import com.sleepycat.je.dbi.EnvironmentImpl;
18 import com.sleepycat.je.rep.ReplicationNetworkConfig;
19 import com.sleepycat.je.rep.net.DataChannelFactory;
20 import com.sleepycat.je.rep.net.InstanceContext;
21 import com.sleepycat.je.rep.net.InstanceLogger;
22 import com.sleepycat.je.rep.net.InstanceParams;
23 import com.sleepycat.je.rep.net.LoggerFactory;
24 import com.sleepycat.je.utilint.LoggerUtils;
25 import com.sleepycat.je.utilint.TracerFormatter;
26 
27 /**
28  * Class for creating DataChannel instances.
29  */
30 public class DataChannelFactoryBuilder {
31 
32     /**
33      * A count of the number of factories for which construction was attempted.
34      */
35     private static final AtomicInteger factoryCount = new AtomicInteger(0);
36 
37     /**
38      * Construct the "default" DataChannelFactory that arises from an empty
39      * DataChannelFactory configuration.
40      */
constructDefault()41     public static DataChannelFactory constructDefault() {
42         return new SimpleChannelFactory();
43     }
44 
45     /**
46      * Construct a DataChannelFactory from the specified network
47      * configuration.
48      * The choice of DataChannelFactory type is determined by the setting
49      * of {@link ReplicationNetworkConfig#CHANNEL_TYPE je.rep.channelType}.
50      *
51      * If set to <code>ssl</code>then the internal SSL implementation is
52      * is used.  If set to <code>custom</code> then a custom channel
53      * factory is constructed based on the setting of
54      *   {@link ReplicationNetworkConfig#CHANNEL_FACTORY_CLASS je.rep.dataChannelFactoryClass}
55      *
56      * If set to <code>basic</code> or not set, SimpleChannelFactory
57      * is instantiated.
58      *
59      * @param repNetConfig The configuration to control factory building
60      * @return a DataChannelFactory
61      * @throws IllegalArgumentException if an invalid configuration
62      * property value or combination of values was specified.
63      */
construct( ReplicationNetworkConfig repNetConfig)64     public static DataChannelFactory construct(
65             ReplicationNetworkConfig repNetConfig)
66         throws IllegalArgumentException {
67 
68         return construct(repNetConfig, (String) null);
69     }
70 
71     /**
72      * Construct a DataChannelFactory from the specified access
73      * configuration.
74      * The choice of DataChannelFactory type is determined by the setting
75      * of {@link ReplicationNetworkConfig#CHANNEL_TYPE je.rep.channelType}.
76      *
77      * If set to <code>ssl</code>then the internal SSL implementation is
78      * is used.  If set to <code>custom</code> then a custom channel
79      * factory is constructed based on the setting of
80      *   {@link ReplicationNetworkConfig#CHANNEL_FACTORY_CLASS je.rep.dataChannelFactoryClass}
81      *
82      * If set to <code>basic</code> or not set, SimpleChannelFactory
83      * is instantiated.
84      *
85      * @param repNetConfig The configuration to control factory building
86      * @param logContext A null-allowable String that contributes to the
87      * logging identifier for the factory.
88      * @return a DataChannelFactory
89      * @throws IllegalArgumentException if an invalid configuration
90      * property value or combination of values was specified.
91      */
construct( ReplicationNetworkConfig repNetConfig, String logContext)92     public static DataChannelFactory construct(
93             ReplicationNetworkConfig repNetConfig, String logContext)
94         throws IllegalArgumentException {
95 
96         final String logName = repNetConfig.getLogName();
97         if (logName.isEmpty() && (logContext == null || logContext.isEmpty())) {
98             return construct(repNetConfig, (LoggerFactory) null);
99         }
100 
101         final String logId;
102         if (logName.isEmpty()) {
103             logId = logContext;
104         } else if (logContext == null || logContext.isEmpty()) {
105             logId = logName;
106         } else {
107             logId = logName + ":" + logContext;
108         }
109         final LoggerFactory loggerFactory = makeLoggerFactory(logId);
110 
111         return construct(repNetConfig, loggerFactory);
112     }
113 
114     /**
115      * Construct a DataChannelFactory from the specified access
116      * configuration.
117      * The choice of DataChannelFactory type is determined by the setting
118      * of {@link ReplicationNetworkConfig#CHANNEL_TYPE je.rep.channelType}.
119      *
120      * If set to <code>ssl</code>then the internal SSL implementation is
121      * is used.  If set to <code>custom</code> then a custom channel
122      * factory is constructed based on the setting of
123      *   {@link ReplicationNetworkConfig#CHANNEL_FACTORY_CLASS je.rep.dataChannelFactoryClass}
124      *
125      * If set to <code>basic</code> or not set, SimpleChannelFactory
126      * is instantiated.
127      *
128      * @param repNetConfig The configuration to control factory building
129      * @param loggerFactory A null-allowable LoggerFactory for use in channel
130      * factory construction
131      * @return a DataChannelFactory
132      * @throws IllegalArgumentException if an invalid configuration
133      * property value or combination of values was specified.
134      */
construct( ReplicationNetworkConfig repNetConfig, LoggerFactory loggerFactory)135     public static DataChannelFactory construct(
136             ReplicationNetworkConfig repNetConfig,
137             LoggerFactory loggerFactory)
138         throws IllegalArgumentException {
139 
140         final String channelType = repNetConfig.getChannelType();
141         final int factoryIndex = factoryCount.getAndIncrement();
142 
143         /*
144          * Build the LoggerFactory if not provided by the caller
145          */
146         if (loggerFactory == null) {
147             String logName = repNetConfig.getLogName();
148             if (logName.isEmpty()) {
149                 logName = Integer.toString(factoryIndex);
150             }
151             loggerFactory = makeLoggerFactory(logName);
152         }
153 
154         final InstanceContext context =
155             new InstanceContext(repNetConfig, loggerFactory);
156 
157         final String factoryClass = repNetConfig.getChannelFactoryClass();
158         if (factoryClass == null || factoryClass.isEmpty()) {
159             if (channelType.equalsIgnoreCase("basic")) {
160                 return new SimpleChannelFactory(
161                     new InstanceParams(context, null));
162             }
163 
164             if (channelType.equalsIgnoreCase("ssl")) {
165                 return new SSLChannelFactory(new InstanceParams(context, null));
166             }
167 
168             throw new IllegalArgumentException(
169                 "The channelType setting '" + channelType + "' is not valid");
170         }
171 
172         final String classParams = repNetConfig.getChannelFactoryParams();
173         final InstanceParams factoryParams =
174             new InstanceParams(context, classParams);
175         return construct(factoryClass, factoryParams);
176     }
177 
178     /**
179      * Constructs a DataChannelFactory implementation.
180      * @param factoryClassName the name of the class to instantiate,
181      * which must implement DataChannelFactory
182      * @param factoryParams the context and factory arguments
183      * @return a newly constructed instance
184      * @throws IllegalArgumentException if the arguments are invalid
185      */
construct( String factoryClassName, InstanceParams factoryParams)186     private static DataChannelFactory construct(
187         String factoryClassName, InstanceParams factoryParams)
188         throws IllegalArgumentException {
189 
190         return (DataChannelFactory) constructObject(
191             factoryClassName, DataChannelFactory.class,
192             "data channel factory",
193             new CtorArgSpec(new Class<?>[] { InstanceParams.class },
194                             new Object[] { factoryParams }));
195     }
196 
197     /**
198      * Instantiates a class based on a configuration specification. This method
199      * looks up a class of the specified name, then finds a constructor with
200      * an argument list that matches the caller's specification, and constructs
201      * an instance using that constructor and validates that the instance
202      * extends or implements the mustImplement class specified.
203      *
204      * @param instClassName the name of the class to instantiate
205      * @param mustImplement a class denoting a required base class or
206      * required implemented interface of the class whose name is
207      * specified by instClassName.
208      * @param miDesc a descriptive term for the mustImplement class
209      * @param ctorArgSpec specifies the required constructor signature and
210      * the values to be passed
211      * @return an instance of the specified class
212      * @throws IllegalArgumentException if any of the input arguments are
213      * invalid
214      */
constructObject(String instClassName, Class<?> mustImplement, String miDesc, CtorArgSpec ctorArgSpec)215     static Object constructObject(String instClassName,
216                                   Class<?> mustImplement,
217                                   String miDesc,
218                                   CtorArgSpec ctorArgSpec)
219         throws IllegalArgumentException {
220 
221         /*
222          * Resolve the class
223          */
224         Class<?> instClass = null;
225         try {
226             instClass = Class.forName(instClassName);
227         } catch (ClassNotFoundException cnfe) {
228             throw new IllegalArgumentException(
229                 "Error resolving " + miDesc + " class " +
230                 instClassName, cnfe);
231         }
232 
233         /*
234          * Find an appropriate constructor for the class.
235          */
236         final Constructor<?> constructor;
237         try {
238             constructor = instClass.getConstructor(ctorArgSpec.argTypes);
239         } catch (NoSuchMethodException nsme) {
240             throw new IllegalArgumentException(
241                 "Unable to find an appropriate constructor for " + miDesc +
242                 " class " + instClassName);
243         }
244 
245         /*
246          * Get an instance of the class.
247          */
248         final Object instObject;
249         try {
250             instObject = constructor.newInstance(ctorArgSpec.argValues);
251         } catch (IllegalAccessException iae) {
252             /* Constructor is not accessible */
253             throw new IllegalArgumentException(
254                 "Error instantiating " + miDesc + " class " + instClassName +
255                 ".  Not accessible?",
256                 iae);
257         } catch (IllegalArgumentException iae) {
258             /* Wrong arguments - should not be possible here */
259             throw new IllegalArgumentException(
260                 "Error instantiating " + miDesc + " class " + instClassName,
261                 iae);
262         } catch (InstantiationException ie) {
263             /* Class is abstract */
264             throw new IllegalArgumentException(
265                 "Error instantiating " + miDesc + " class " + instClassName +
266                 ". Class is abstract?",
267                 ie);
268         } catch (InvocationTargetException ite) {
269             /* Exception thrown within constructor */
270             throw new IllegalArgumentException(
271                 "Error instantiating " + miDesc + " class " + instClassName +
272                 ". Exception within constructor",
273                 ite);
274         }
275 
276         /*
277          * In this context, the class must implement the specified
278          * interface.
279          */
280         if (! (mustImplement.isAssignableFrom(instObject.getClass()))) {
281             throw new IllegalArgumentException(
282                 "The " + miDesc + " class " +  instClassName +
283                 " does not implement " + mustImplement.getName());
284         }
285 
286         return instObject;
287     }
288 
289     /**
290      * Creates a logger factory based on an EnvironmentImpl
291      *
292      * @param envImpl a non-null EnvironmentImpl
293      */
makeLoggerFactory(EnvironmentImpl envImpl)294     public static LoggerFactory makeLoggerFactory(EnvironmentImpl envImpl) {
295         if (envImpl == null) {
296             throw new IllegalArgumentException("envImpl must not be null");
297         }
298 
299         return new ChannelLoggerFactory(envImpl, null /* formatter */);
300     }
301 
302     /**
303      * Creates a logger factory based on a fixed string
304      *
305      * @param prefix a fixed string to be used as logger prefix
306      */
makeLoggerFactory(String prefix)307     public static LoggerFactory makeLoggerFactory(String prefix) {
308         if (prefix == null) {
309             throw new IllegalArgumentException("prefix must not be null");
310         }
311 
312         final Formatter formatter = new ChannelFormatter(prefix);
313 
314         return new ChannelLoggerFactory(null, /* envImpl */ formatter);
315     }
316 
317 
318     /**
319      * A simple class that captures the proposed formal and actual argument
320      * lists to match against possible constructors.
321      */
322     static class CtorArgSpec {
323         private final Class<?>[] argTypes;
324         private final Object[] argValues;
325 
CtorArgSpec(Class<?>[] argTypes, Object[] argValues)326         CtorArgSpec(Class<?>[] argTypes, Object[] argValues) {
327             this.argTypes = argTypes;
328             this.argValues = argValues;
329         }
330     }
331 
332     /**
333      * A simple implementation of LoggerFactory that encapsulates the
334      * necessary information to do JE environment-friendly logging without
335      * needing to know JE HA internal logging.
336      */
337     static class ChannelLoggerFactory implements LoggerFactory {
338         private final EnvironmentImpl envImpl;
339         private final Formatter formatter;
340 
341         /**
342          * Creates a LoggerFactory for use in construction of channel
343          * objects. The caller should supply either an EnvironmentImpl or a
344          * Formatter object.
345          *
346          * @param envImpl a possibly-null EnvironmentImpl
347          * @param formatter a possible null formatter
348          */
ChannelLoggerFactory(EnvironmentImpl envImpl, Formatter formatter)349         ChannelLoggerFactory(EnvironmentImpl envImpl,
350                              Formatter formatter) {
351             this.envImpl = envImpl;
352             this.formatter = formatter;
353         }
354 
355         /**
356          * @see LoggerFactory#getLogger(Class)
357          */
358         @Override
getLogger(Class<?> clazz)359         public InstanceLogger getLogger(Class<?> clazz) {
360             final Logger logger;
361             if (envImpl == null) {
362                 logger = LoggerUtils.getLoggerFormatterNeeded(clazz);
363             } else {
364                 logger = LoggerUtils.getLogger(clazz);
365             }
366             return new ChannelInstanceLogger(envImpl, formatter, logger);
367         }
368     }
369 
370     /**
371      * A simple implementation of InstanceLogger that encapuslates the
372      * necessary information to do JE environment-friendly logging without
373      * needing to know JE logging rules.
374      */
375     static class ChannelInstanceLogger implements InstanceLogger {
376         private final EnvironmentImpl envImpl;
377         private final Formatter formatter;
378         private final Logger logger;
379 
380         /**
381          * Creates a ChannelInstanceLogger for use in construction of channel
382          * objects. The caller should supply either an EnvironmentImpl or a
383          * Formatter object.
384          *
385          * @param envImpl a possibly-null EnvironmentImpl
386          * @param formatter a possible null formatter
387          * @param logger a logger created via LoggerUtils.getLogger()
388          */
ChannelInstanceLogger(EnvironmentImpl envImpl, Formatter formatter, Logger logger)389         ChannelInstanceLogger(EnvironmentImpl envImpl,
390                               Formatter formatter,
391                               Logger logger) {
392             this.envImpl = envImpl;
393             this.formatter = formatter;
394             this.logger = logger;
395         }
396 
397         /**
398          * @see InstanceLogger#log(Level, String)
399          */
400         @Override
log(Level logLevel, String msg)401         public void log(Level logLevel, String msg) {
402             LoggerUtils.logMsg(logger, envImpl, formatter, logLevel, msg);
403         }
404     }
405 
406     /**
407      * Formatter for log messages
408      */
409     static class ChannelFormatter extends TracerFormatter {
410         private final String id;
411 
ChannelFormatter(String id)412         ChannelFormatter(String id) {
413             super();
414             this.id = id;
415         }
416 
417         @Override
appendEnvironmentName(StringBuilder sb)418         protected void appendEnvironmentName(StringBuilder sb) {
419             sb.append(" [" + id + "]");
420         }
421     }
422 }
423