-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathKind.java
More file actions
347 lines (303 loc) · 11.5 KB
/
Kind.java
File metadata and controls
347 lines (303 loc) · 11.5 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
340
341
342
343
344
345
346
347
/* 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.data;
import com.google.gdata.util.Namespaces;
import com.google.gdata.util.ServiceException;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* The Kind class defines annotation types, interfaces and static helper
* methods for GData Kind extension handling. A GData <i>Kind</i> refers to
* a specific extension profile configuration for an Atom feed/entry or RSS
* channel/item.
*
*
*/
public class Kind {
/**
* Caches the mappings from a kind term to the {@link Adaptor} classes that
* handle the kind. Since these are configured by JAR-based metadata,
* they are guaranteed to be constant once loaded unless/until the
* classloader for GData java library is bounced.
*/
private static Map<String, List<Class<Adaptor>>> kindAdaptors =
new HashMap<String, List<Class<Adaptor>>>();
/**
* The Term annnotation type is used to annotate {@link Adaptor}
* classes to declare the GData kind {@link Category} term value(s)
* implemented by the adaptor type.
*/
@Target(ElementType.TYPE)
public @interface Term {
/**
* Specifies the term value within the {link Namespace.gKind} scheme
* that is handled by the {@link Adaptor} class.
*/
public String value();
}
/**
* The Adaptable interface is implemented by GData {@link ExtensionPoint}
* types that can be flexible adapted based upon the presence of GData
* kind category elements.
*/
public interface Adaptable {
/**
* Associates a new {@link Adaptor} with this {@code Adaptable} instance.
*/
void addAdaptor(Adaptor adaptor);
/**
* Returns the collection of {@link Adaptor} instances associated with the
* this {@code Adaptable} instance.
*/
Collection<Adaptor> getAdaptors();
/**
* Returns a {@link Adaptor} instance associated with this
* {@code Adaptable} instance of the specified type, or {code null}
* if none is available..
*/
<E extends Adaptor> E getAdaptor(Class<E> adaptorClass);
}
/**
* The Adaptor interface is implemented by {@link Extension} classes
* that provide extension declaration and data modeling support for
* specific GData kinds.
*/
public interface Adaptor {
/**
* Declares the {@link ExtensionDescription} of each {@link Extension}
* expected by the implementing {@link ExtensionPoint} in the target
* profile. This API should generally not be called directly by clients;
* the {@link ExtensionProfile#addDeclarations(Kind.Adaptor)} method should
* be used to declare extensions.
*
* @param extProfile the profile that should be extended.
* @see ExtensionProfile#addDeclarations(Kind.Adaptor)
*/
void declareExtensions(ExtensionProfile extProfile);
}
/**
* A simple helper class implementation of the {@link Adaptable} interface.
* Classes that need to implement {@code Adaptable} can construct an
* instance of this class and delegate to it.
*/
public static class AdaptableHelper implements Adaptable {
private List<Kind.Adaptor> adaptors = new ArrayList<Kind.Adaptor>();
public void addAdaptor(Kind.Adaptor adaptor) {
adaptors.add(adaptor);
}
public Collection<Kind.Adaptor> getAdaptors() {
return adaptors;
}
public <E extends Kind.Adaptor> E getAdaptor(Class<E> adaptorClass) {
for (Kind.Adaptor adaptor : adaptors) {
if (adaptor.getClass().equals(adaptorClass)) {
return adaptorClass.cast(adaptor);
}
}
return null;
}
}
/**
* The AdaptorException class defines a simple {@link ServiceException}
* type that is thrown on kind adaptation failures.
*/
public static class AdaptorException extends ServiceException {
public AdaptorException(String message) {
super(message);
setHttpErrorCodeOverride(HttpURLConnection.HTTP_INTERNAL_ERROR);
}
public AdaptorException(String message, Throwable cause) {
super(message, cause);
setHttpErrorCodeOverride(HttpURLConnection.HTTP_INTERNAL_ERROR);
}
public AdaptorException(Throwable cause) {
super(cause);
setHttpErrorCodeOverride(HttpURLConnection.HTTP_INTERNAL_ERROR);
}
}
public static boolean isKindCategory(Category category) {
return Namespaces.gKind.equals(category.getScheme());
}
/**
* Returns the kind service name associatd with a particular Kind category
* term value. It converts a kind term URI to a service file name that
* is used for dynamic discovery of {@link Adaptor} class implementations
* for the kind.
*/
public static String getKindServiceName(String kindTerm) {
StringBuilder serviceName = new StringBuilder(kindTerm.length());
try {
URL termUrl = new URL(kindTerm);
// Invert any dotted components of the host name
String [] hostComponents = termUrl.getHost().split("\\W");
int lastIndex = hostComponents.length - 1;
for (int i = lastIndex; i >= 0; i--) {
if (i != lastIndex) {
serviceName.append(".");
}
serviceName.append(hostComponents[i]);
}
// Convert the path, substituting dot separators for path
// element separators
String [] pathComponents = termUrl.getPath().split("\\W");
for (int i = 0; i < pathComponents.length; i++) {
if (pathComponents[i].length() > 0) {
serviceName.append(".");
serviceName.append(pathComponents[i]);
}
}
// Convert the ref (if any), substituting dot separators.
if (termUrl.getRef() != null) {
String [] refComponents = termUrl.getRef().split("\\W");
for (int i = 0; i < refComponents.length; i++) {
if (refComponents[i].length() > 0) {
serviceName.append(".");
serviceName.append(refComponents[i]);
}
}
}
} catch (java.net.MalformedURLException mue) {
throw new IllegalArgumentException("Kind term must be a valid URL", mue);
}
return serviceName.toString();
}
/**
* Returns that {@link Adaptor} class that handles the
* declaration of extensions within an {@link ExtensionProfile} based
* upon the kind term value. A return value of {@code null} indicates
* that no adaptor class could be located for this cobintation of kind and
* {@link Adaptable} type.
*/
public static Class<Adaptor> getAdaptorClass(String kindTerm,
Adaptable adaptable)
throws AdaptorException {
ClassLoader cl = adaptable.getClass().getClassLoader();
List<Class<Adaptor>> adaptorList = kindAdaptors.get(kindTerm);
if (adaptorList == null) {
// Lazily load the adaptor list for a kind on first usage and store
// in the cache.
adaptorList = new ArrayList<Class<Adaptor>>();
String termService = getKindServiceName(kindTerm);
InputStream serviceStream;
try {
serviceStream = cl.getResourceAsStream("META-INF/gdata/kinds/"
+ termService);
if (serviceStream == null) {
return null;
}
BufferedReader rdr =
new BufferedReader(new InputStreamReader(serviceStream));
String line;
while ((line = rdr.readLine()) != null) {
if (line.charAt(0) == '#') { // comment line
continue;
}
adaptorList.add((Class<Adaptor>) cl.loadClass(line));
}
} catch (IOException ioe) {
throw new AdaptorException("Unable to load Adaptor service info", ioe);
} catch (ClassNotFoundException cnfe) {
throw new AdaptorException("Unable to load Adaptor class", cnfe);
}
kindAdaptors.put(kindTerm, adaptorList);
}
// A mix-in adaptor type will only have one mapping, that can be used
// for all valid contexts since it doesn't rely upon inheritance.
if (adaptorList.size() == 1) {
return adaptorList.get(0);
}
Class<Adaptor> adaptorclass = null;
// Inheritance based adaptation: Look for Adaptor instance that shares
// a superclass relationship with the input Adaptable (for example,
// both derive from BaseFeed).
for (Class<Adaptor> adaptorClass : adaptorList) {
Class<? extends Adaptable> checkClass = adaptable.getClass();
while (Adaptable.class.isAssignableFrom(checkClass)) {
if (checkClass.isAssignableFrom(adaptorClass)) {
return adaptorClass;
}
checkClass = (Class<? extends Adaptable>) checkClass.getSuperclass();
}
}
return null;
}
/**
* Returns an {@link Adaptor} instance associated with the specified
* kind that is associated with the target {@link Adaptable}. Returns
* {@code null} if no Adaptor can be found.
*/
static public Adaptor getAdaptor(String kindTerm, Adaptable adaptable)
throws AdaptorException {
Class<Adaptor> adaptorClass = getAdaptorClass(kindTerm, adaptable);
if (adaptorClass == null) {
return null;
}
Adaptor adaptor = adaptable.getAdaptor(adaptorClass);
if (adaptor == null) {
// Look for an adaptor constructor that can take the adaptable
// instance as an argument.
Constructor<?> adaptorConstructor = null;
Class<?> constructorArgClass = adaptable.getClass();
while (constructorArgClass != null) {
try {
adaptorConstructor = adaptorClass.getConstructor(constructorArgClass);
break;
} catch (NoSuchMethodException nsme) {
// Move on to parent of adaptor class and check again
constructorArgClass = constructorArgClass.getSuperclass();
}
}
// If not found, look for one with a null-arg constructor. This
// means it is a mix-in style kind, rather than an entry or feed
// extension.
if (adaptorConstructor == null) {
try {
adaptorConstructor = adaptorClass.getConstructor();
} catch (NoSuchMethodException nsme) {
throw new AdaptorException("Unable to construct Adaptor " +
adaptorClass + " instance for " +
adaptable.getClass());
}
}
// Construct the new Adaptor instance
try {
if (constructorArgClass == null) {
adaptor = (Adaptor)adaptorConstructor.newInstance();
} else {
adaptor = (Adaptor)adaptorConstructor.newInstance(adaptable);
}
} catch (RuntimeException re) {
throw re;
} catch (Exception e) {
throw new AdaptorException("Unable to create kind Adaptor", e);
}
// Save the adaptable
adaptable.addAdaptor(adaptor);
}
return adaptor;
}
}