forked from google/gdata-java-client
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathVersionRegistry.java
More file actions
339 lines (310 loc) · 12.9 KB
/
Copy pathVersionRegistry.java
File metadata and controls
339 lines (310 loc) · 12.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
/* Copyright (c) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gdata.util;
import com.google.common.annotations.VisibleForTesting;
import com.google.gdata.client.Service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* The VersionRegistry class is used to manage and retrieve version information
* about executing services. The registry supports the ability to configure
* versions for a running thread (via the {@link #setThreadVersion(Version)}
* method) or global defaults that will apply to all threads (using the
* {@link #addDefaultVersion(Version, boolean)} method. Thread defaults will
* have precedence over global defaults if present for the same service.
*
* The class provides a singleton instance that is being used to manage version
* information. This instance is initialized by the {@link #ensureRegistry()}
* method. The active VersionRegistry instance can be retrieved using the
* {@link #get()} method. This method will throw an
* {@link IllegalStateException} if the version registry has not been
* initialized to aid in the detection of when version-conditional code is being
* executed in an environment where versions have net been configured.
*
* The {@link VersionRegistry#getVersion(Class)} method can be used to request
* the version information for a particular service.
*
* A model for writing version conditional code based upon the registry is:
* <code>
* Version myServiceVersion =
* VersionRegistry.get().getVersion(MyService.class);
* if (myServiceVersion.isCompatible(MyService.VERSIONS.V1) {
* ... execute V1-specific handling ...
* }
* </code>
*
* VersionRegistry access is thread-safe.
*/
public class VersionRegistry {
/**
* Singleton registry instance. The singleton is lazily initialized when the
* {@link #ensureRegistry()} method is called. The reason for this design is
* to support the detect of version-conditional code running in unit tests.
* Such tests need to be run in a version-aware test environment (that will
* validate the behavior against all valid versions), so having a model so
* that they will fail by default is helpful to guarantee this.
*/
private static VersionRegistry versionRegistry;
/**
* Maintains the per-thread version information. The field may be
* {@code null} if thread tracking is not enabled and the thread local value
* may be {@code null} if no versions have been set for the current thread.
*/
private ThreadLocal<List<Version>> threadVersions =
new ThreadLocal<List<Version>>();
/**
* Maintains the global defaults.
*/
private List<Version> defaultVersions = new ArrayList<Version>();
/**
* Returns the current VersionRegistry, creating it if necessary. The
* {@link #get()} method is preferred for most registry usage, as it enables
* the discovery of the execution of version-conditional code in an
* environment (such as unit test cases) where versioning has not been
* properly configured.
*/
public static synchronized VersionRegistry ensureRegistry() {
if (versionRegistry == null) {
versionRegistry = new VersionRegistry();
}
return versionRegistry;
}
/**
* Resets the VersionRegistry instance to {@code null}. This means that any
* subsequent attempts to run version-specific code without version
* configuration will result in an {@link IllegalStateException} in
* {@link #get()}.
*/
@VisibleForTesting
static void reset() {
versionRegistry = null;
}
/**
* Returns the version registry being used to manage version information.
* @return the active version registry instance.
* @throws IllegalStateException if the registry has not been initialized.
*/
public static final VersionRegistry get() {
if (versionRegistry == null) {
// This should never happen for client, server, or code running in a
// unit test context. Missing version information indicates that the
// version registry has not been properly initialized to meet the
// expectations of version-dependent code. In the case of test
// execution, this generally means the test should be annotated to
// indicate a version dependency (see the TestVersion annotation) and
// also should be run in the context of a VersionedTestSuite that
// ensures all supported versions are tested.
throw new IllegalStateException("Uninitialized version registry");
}
return versionRegistry;
}
/**
* Constructs a new Version instance based upon the value of a Java system
* property associated with a {@link Service} class. The system property name
* is computed from the service class name with ".version" appended. The
* syntax of the property value is {@code "[service]<major>[.<minor>]"}. The
* default value of the service is assumed to be the initiating or target
* service and the minor revision will be assumed to be zero if not present.
* If the associated system property is not set, the method will return
* {@code null}.
*
* @param serviceClass service class to use in computing the version property
* name.
* @return the {@link Version} computed from the property of {@code null} if
* the property is not set.
* @throws IllegalStateException if the property value does not contain valid
* revision information.
*/
public static Version getVersionFromProperty(
Class<? extends Service> serviceClass) {
String propertyName = serviceClass.getName() + ".version";
String versionProperty = System.getProperty(propertyName);
if (versionProperty == null) {
return null;
}
try {
return new Version(serviceClass, versionProperty);
} catch (IllegalArgumentException iae) {
throw new IllegalStateException(
"Invalid version property value: " + propertyName, iae);
}
}
/**
* Takes a list of {@link Version} instances and merges it into another
* list. A version in the source list will overwrite any value for the
* same service (if any) in the target list.
* @param target the target list of versions to merge into.
* @param source the source list of versions that will be merged.
*/
@VisibleForTesting
static void mergeVersions(List<Version> target, List<Version> source) {
// Check for conflicts with target list before making any changes,
// accumulating the list of changed versions.
for (Version checkVersion : source) {
Version currentVersion =
Version.findServiceVersion(target, checkVersion.getServiceClass());
if (currentVersion != null) {
target.remove(currentVersion);
}
}
// Add all of the new versions.
target.addAll(source);
}
/**
* Takes a {@link Version} instance and merges it into another
* list, validating that any duplicate information for a given service
* is a compatible version.
* @param target the target list of versions to merge into.
* @param source the source version that will be merged.
*/
@VisibleForTesting
static void mergeVersions(List<Version> target, Version source) {
mergeVersions(target, Arrays.asList(new Version [] { source }));
}
/**
* Returns the list of default versions for the registry. The default version
* is the version that will be used if no version is explicitly selected.
*
* @return list of default versions.
*/
public List<Version> getDefaultVersions() {
return defaultVersions;
}
/**
* Adds a default version to the version registry. This will overwrite any
* existing default version for the same service.
*
* @param newDefault default version to add to the registry
* (not <code>null</code>)
* @param includeImplied if {@code true}, indicates that all implied versions
* associated with the new default should be set as defaults too.
*/
public void addDefaultVersion(Version newDefault,
boolean includeImplied) {
// Implement the addition using a copy into a new array. This is done to
// avoid requiring full synchronization of access to defaultVersions, where
// additions will be infrequent and often happen at initialization time.
ArrayList<Version> newDefaults = new ArrayList<Version>(defaultVersions);
if (includeImplied) {
mergeVersions(newDefaults, newDefault.getImpliedVersions());
} else {
mergeVersions(newDefaults, newDefault);
}
// Replace the current defaults with the updated list.
defaultVersions = Collections.unmodifiableList(newDefaults);
}
/**
* Sets the desired version for the current thread to the provided values.
* This method will update any existing request version information set by
* defaults or a previous call to this method. The specified version (and
* any related implied versions} will be set for the current thread until the
* {@link #resetThreadVersion()} method is called to reset to the version
* information back to the default state.
*
* @param version the new active version for this request.
*/
public void setThreadVersion(Version version) {
// Set the thread local to the list of versions implied by the requested
// version.
threadVersions.set(
Collections.unmodifiableList(version.getImpliedVersions()));
}
/**
* Returns the list of versions associated with the current thread or
* {@code null} if there are currently no thread versions.
*
* @return thread version list or {@code null}
*/
public List<Version> getThreadVersions() {
return threadVersions.get();
}
/**
* Resets the version information for the current thread back to the
* default state.
*/
public void resetThreadVersion() {
if (threadVersions != null) {
threadVersions.remove();
}
}
/**
* Returns the the current list of active versions. This list takes both
* global defaults and thread versions into account.
*/
@VisibleForTesting
List<Version> getVersions() {
List<Version> defaultList = getDefaultVersions();
List<Version> threadList = getThreadVersions();
if (threadList == null) {
return defaultList;
}
List<Version> combinedList =
new ArrayList<Version>(defaultList.size() + threadList.size());
combinedList.addAll(defaultList);
mergeVersions(combinedList, threadList);
return combinedList;
}
/**
* Returns the version of a service.
*
* @param serviceClass of the service to return.
* @return version of the service.
* @throws IllegalStateException if no version information could be found for
* the requested service.
*/
public Version getVersion(Class<? extends Service> serviceClass) {
Version v = null;
List<Version> threadList = getThreadVersions();
if (threadList != null) {
v = Version.findServiceVersion(threadList, serviceClass);
}
if (v == null) {
v = Version.findServiceVersion(getDefaultVersions(), serviceClass);
if (v == null) {
// This should never happen for client, server, or code running in a
// unit test context. Missing version information indicates that the
// version registry has not been properly initialized to meet the
// expectations of version-dependent code. In the case of test
// execution, this generally means the test should be annotated to
// indicate a version dependency (see the TestVersion annotation) and
// also should be run in the context of a VersionedTestSuite that
// ensures all supported versions are tested.
throw new IllegalStateException(
"Attempt to access version information for unversioned service:" +
serviceClass);
}
}
return v;
}
/**
* Resets the VersionRegistry to a clean state with no thread local
* configuration and the specified set of version defaults.
*
* @param initialDefaults the list of default versions that should be used to
* initialize the version registry, or {@code null} for an empty
* default list.
*/
@VisibleForTesting
public synchronized void reset(List<Version> initialDefaults) {
threadVersions = new ThreadLocal<List<Version>>();
if (initialDefaults != null) {
defaultVersions = new ArrayList<Version>(initialDefaults);
} else {
defaultVersions = new ArrayList<Version>();
}
}
}