View Javadoc
1   /**
2    * Copyright (C) 2010-14 pvmanager developers. See COPYRIGHT.TXT
3    * All rights reserved. Use is subject to license terms. See LICENSE.TXT
4    */
5   package org.epics.pvmanager.jca;
6   
7   import gov.aps.jca.CAException;
8   import gov.aps.jca.Context;
9   import gov.aps.jca.JCALibrary;
10  import gov.aps.jca.Monitor;
11  import gov.aps.jca.jni.JNIContext;
12  import java.lang.reflect.InvocationTargetException;
13  import java.lang.reflect.Method;
14  import java.security.AccessController;
15  import java.security.PrivilegedAction;
16  import java.util.logging.Level;
17  import java.util.logging.Logger;
18  
19  /**
20   * Builder for {@link JCADataSource}. Given the moderate number of configuration
21   * parameters, the builder allows to set only the ones the user needs to set.
22   * <p>
23   * Refer to each parameter for their meaning and default.
24   *
25   * @author carcassi
26   */
27  public class JCADataSourceBuilder {
28      private static final Logger log = Logger.getLogger(JCADataSource.class.getName());
29      
30      Context jcaContext;
31      int monitorMask = Monitor.VALUE | Monitor.ALARM;
32      JCATypeSupport typeSupport;
33      boolean dbePropertySupported  = false;
34      Boolean varArraySupported;
35      boolean rtypValueOnly = false;
36      boolean honorZeroPrecision = true;
37  
38      /**
39       * The class name for the implementation of JCA.
40       * <p>
41       * Default is {@link JCALibrary#CHANNEL_ACCESS_JAVA}.
42       * 
43       * @param className the class name of the jca implementation
44       * @return this
45       */
46      public JCADataSourceBuilder jcaContextClass(String className) {
47          if (jcaContext != null) {
48              throw new IllegalStateException("You should call either jcaContextClass or jcaContext once.");
49          }
50          this.jcaContext = createContext(className);
51          return this;
52      }
53      
54      /**
55       * The jca context to use. This allows complete customization
56       * of the jca context.
57       * <p>
58       * By default, will be automatically
59       * created from the {@link #jcaContextClass(java.lang.String) }.
60       * 
61       * @param jcaContext the context
62       * @return this
63       */
64      public JCADataSourceBuilder jcaContext(Context jcaContext) {
65          if (jcaContext == null) {
66              throw new IllegalStateException("You should call once either jcaContextClass or jcaContext.");
67          }
68          this.jcaContext = jcaContext;
69          return this;
70      }
71  
72      /**
73       * The mask used for the monitor notifications. This should be a combination
74       * of {@link Monitor#VALUE}, {@link Monitor#ALARM}, ...
75       * <p>
76       * Default is {@code Monitor.VALUE | Monitor.ALARM }.
77       * 
78       * @param monitorMask the monitor mask
79       * @return this
80       */
81      public JCADataSourceBuilder monitorMask(int monitorMask) {
82          this.monitorMask = monitorMask;
83          return this;
84      }
85      
86      /**
87       * Changes the way JCA DBR types are mapped to types supported in pvamanger.
88       * <p>
89       * Default includes support for the VTypes (i.e. {@link JCAVTypeAdapterSet}).
90       * 
91       * @param typeSupport the custom type support
92       * @return this
93       */
94      public JCADataSourceBuilder typeSupport(JCATypeSupport typeSupport) {
95          this.typeSupport = typeSupport;
96          return this;
97      }
98  
99      /**
100      * Whether a separate monitor should be used for listening to metadata
101      * changes.
102      * <p>
103      * Default is false.
104      * 
105      * @param dbePropertySupported if true, metadata changes will trigger notification
106      * @return this
107      */
108     public JCADataSourceBuilder dbePropertySupported(boolean dbePropertySupported) {
109         this.dbePropertySupported = dbePropertySupported;
110         return this;
111     }
112 
113     /**
114      * If true, monitors will setup using "0" length, which will make
115      * the server a variable length array in return (if supported) or a "0"
116      * length array (if not supported). Variable array support was added
117      * to EPICS 3.14.12.2 and to CAJ 1.1.10.
118      * <p>
119      * By default it tries to auto-detected whether the client library
120      * implements the proper checks.
121      * 
122      * @param varArraySupported true will enable
123      * @return this
124      */
125     public JCADataSourceBuilder varArraySupported(boolean varArraySupported) {
126         this.varArraySupported = varArraySupported;
127         return this;
128     }
129     
130     /**
131      * If true, for fields that match ".*\.RTYP.*" only the value will be
132      * read; alarm and time will be created at client side. Version of EPICS
133      * before 3.14.11 do not send correct data (would send only the value),
134      * which would make the client behave incorrectly.
135      * <p>
136      * Default is false.
137      * 
138      * @param rtypValueOnly true will enable
139      * @return this
140      */
141     public JCADataSourceBuilder rtypValueOnly(boolean rtypValueOnly) {
142         this.rtypValueOnly = rtypValueOnly;
143         return this;
144     }
145     
146     /**
147      * If true, the formatter returned by the VType will show
148      * no decimal digits (assumes it was configured);
149      * if false, it will return all the digit (assumes it wasn't configured).
150      * <p>
151      * Default is true.
152      * 
153      * @param honorZeroPrecision whether the formatter should treat 0 precision as meaningful
154      * @return this
155      */
156     public JCADataSourceBuilder honorZeroPrecision(boolean honorZeroPrecision) {
157         this.honorZeroPrecision = honorZeroPrecision;
158         return this;
159     }
160     
161     /**
162      * Creates a new data source.
163      * 
164      * @return a new data source
165      */
166     public JCADataSource build() {
167         return new JCADataSource(this);
168     }
169     
170     /**
171      * Determines whether the context supports variable arrays
172      * or not.
173      * 
174      * @param context a JCA Context
175      * @return true if supports variable sized arrays
176      */
177     static boolean isVarArraySupported(Context context) {
178         try {
179             Class cajClazz = Class.forName("com.cosylab.epics.caj.CAJContext");
180             if (cajClazz.isInstance(context)) {
181                 return !(context.getVersion().getMajorVersion() <= 1 && context.getVersion().getMinorVersion() <= 1 && context.getVersion().getMaintenanceVersion() <=9);
182             }
183         } catch (ClassNotFoundException ex) {
184             // Can't be CAJ, fall back to JCA
185         }
186         
187         if (context instanceof JNIContext) {
188             try {
189                 Class<?> jniClazz = Class.forName("gov.aps.jca.jni.JNI");
190                 final Method method = jniClazz.getDeclaredMethod("_ca_getRevision", new Class<?>[0]);
191                 // The field is actually private, so we need to make it accessible
192                 AccessController.doPrivileged(new PrivilegedAction<Object>() {
193 
194                     @Override
195                     public Object run() {
196                         method.setAccessible(true);
197                         return null;
198                     }
199                     
200                 });
201                 Integer integer = (Integer) method.invoke(null, new Object[0]);
202                 return (integer >= 13);
203             } catch (Exception ex) {
204                 log.log(Level.SEVERE, "Couldn't detect varArraySupported", ex);
205             }
206         }
207         
208         return true;
209     }
210     
211     /**
212      * Creates a context from the class name.
213      * 
214      * @param className the class name
215      * @return a new context
216      */
217     static Context createContext(String className) {
218         try {
219             JCALibrary jca = JCALibrary.getInstance();
220             return jca.createContext(className);
221         } catch (CAException ex) {
222             log.log(Level.SEVERE, "JCA context creation failed", ex);
223             throw new RuntimeException("JCA context creation failed", ex);
224         }
225     }    
226 }