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 }