1
1
/*
2
- * Copyright 2012-2016 the original author or authors.
2
+ * Copyright 2012-2017 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
16
16
package org .springframework .hateoas ;
17
17
18
18
import java .io .Serializable ;
19
+ import java .util .Arrays ;
19
20
import java .util .Collections ;
20
21
import java .util .HashMap ;
22
+ import java .util .HashSet ;
21
23
import java .util .List ;
22
24
import java .util .Map ;
25
+ import java .util .Set ;
23
26
import java .util .regex .Matcher ;
24
27
import java .util .regex .Pattern ;
25
28
37
40
* Value object for links.
38
41
*
39
42
* @author Oliver Gierke
43
+ * @author Greg Turnquist
40
44
*/
41
45
@ XmlType (name = "link" , namespace = Link .ATOM_NAMESPACE )
42
46
@ JsonIgnoreProperties ("templated" )
@@ -55,6 +59,11 @@ public class Link implements Serializable {
55
59
56
60
@ XmlAttribute private String rel ;
57
61
@ XmlAttribute private String href ;
62
+ @ XmlAttribute private String hreflang ;
63
+ @ XmlAttribute private String media ;
64
+ @ XmlAttribute private String title ;
65
+ @ XmlAttribute private String type ;
66
+ @ XmlAttribute private String deprecation ;
58
67
@ XmlTransient @ JsonIgnore private UriTemplate template ;
59
68
60
69
/**
@@ -85,14 +94,35 @@ public Link(String href, String rel) {
85
94
*/
86
95
public Link (UriTemplate template , String rel ) {
87
96
88
- Assert .notNull (template , "UriTempalte must not be null!" );
97
+ Assert .notNull (template , "UriTemplate must not be null!" );
89
98
Assert .hasText (rel , "Rel must not be null or empty!" );
90
99
91
100
this .template = template ;
92
101
this .href = template .toString ();
93
102
this .rel = rel ;
94
103
}
95
104
105
+ /**
106
+ * Creates a new {@link Link} to the given URI with the given rel, hreflang, media, title, and type.
107
+ *
108
+ * @param href must not be {@literal null} or empty.
109
+ * @param rel must not be {@literal null} or empty.
110
+ * @param hreflang
111
+ * @param media
112
+ * @param title
113
+ * @param type
114
+ * @param deprecation
115
+ */
116
+ public Link (String href , String rel , String hreflang , String media , String title , String type , String deprecation ) {
117
+
118
+ this (href , rel );
119
+ this .hreflang = hreflang ;
120
+ this .media = media ;
121
+ this .title = title ;
122
+ this .type = type ;
123
+ this .deprecation = deprecation ;
124
+ }
125
+
96
126
/**
97
127
* Empty constructor required by the marshalling framework.
98
128
*/
@@ -118,6 +148,51 @@ public String getRel() {
118
148
return rel ;
119
149
}
120
150
151
+ /**
152
+ * Returns the hreflang of the link.
153
+ *
154
+ * @return
155
+ */
156
+ public String getHreflang () {
157
+ return hreflang ;
158
+ }
159
+
160
+ /**
161
+ * Returns the media of the link.
162
+ *
163
+ * @return
164
+ */
165
+ public String getMedia () {
166
+ return media ;
167
+ }
168
+
169
+ /**
170
+ * Returns the title of the link.
171
+ *
172
+ * @return
173
+ */
174
+ public String getTitle () {
175
+ return title ;
176
+ }
177
+
178
+ /**
179
+ * Returns the type of the link
180
+ *
181
+ * @return
182
+ */
183
+ public String getType () {
184
+ return type ;
185
+ }
186
+
187
+ /**
188
+ * Returns link about deprecation of this link.
189
+ *
190
+ * @return
191
+ */
192
+ public String getDeprecation () {
193
+ return deprecation ;
194
+ }
195
+
121
196
/**
122
197
* Returns a {@link Link} pointing to the same URI but with the given relation.
123
198
*
@@ -137,6 +212,61 @@ public Link withSelfRel() {
137
212
return withRel (Link .REL_SELF );
138
213
}
139
214
215
+ /**
216
+ * Returns a {@link Link} with the {@code hreflang} attribute filled out.
217
+ *
218
+ * @param hreflang
219
+ * @return
220
+ */
221
+ public Link withHreflang (String hreflang ) {
222
+ Assert .hasText (hreflang , "hreflang must not be null or empty!" );
223
+ return new Link (this .href , this .rel , hreflang , this .media , this .title , this .type , this .deprecation );
224
+ }
225
+
226
+ /**
227
+ * Returns a {@link Link} with the {@code media} attribute filled out.
228
+ *
229
+ * @param media
230
+ * @return
231
+ */
232
+ public Link withMedia (String media ) {
233
+ Assert .hasText (media , "media must not be null or empty!" );
234
+ return new Link (this .href , this .rel , this .hreflang , media , this .title , this .type , this .deprecation );
235
+ }
236
+
237
+ /**
238
+ * Returns a {@link Link} with the {@code title} attribute filled out.
239
+ *
240
+ * @param title
241
+ * @return
242
+ */
243
+ public Link withTitle (String title ) {
244
+ Assert .hasText (title , "title must not be null or empty!" );
245
+ return new Link (this .href , this .rel , this .hreflang , this .media , title , this .type , this .deprecation );
246
+ }
247
+
248
+ /**
249
+ * Returns a {@link Link} with the {@code type} attribute filled out.
250
+ *
251
+ * @param type
252
+ * @return
253
+ */
254
+ public Link withType (String type ) {
255
+ Assert .hasText (type , "type must not be null or empty!" );
256
+ return new Link (this .href , this .rel , this .hreflang , this .media , this .title , type , this .deprecation );
257
+ }
258
+
259
+ /**
260
+ * Returns a {@link Link} with the {@code deprecation} attribute filled out.
261
+ *
262
+ * @param deprecation
263
+ * @return
264
+ */
265
+ public Link withDeprecation (String deprecation ) {
266
+ Assert .hasText (deprecation , "deprecation must not be null or empty!" );
267
+ return new Link (this .href , this .rel , this .hreflang , this .media , this .title , this .type , deprecation );
268
+ }
269
+
140
270
/**
141
271
* Returns the variable names contained in the template.
142
272
*
@@ -212,7 +342,20 @@ public boolean equals(Object obj) {
212
342
213
343
Link that = (Link ) obj ;
214
344
215
- return this .href .equals (that .href ) && this .rel .equals (that .rel );
345
+ return
346
+ this .href .equals (that .href )
347
+ &&
348
+ this .rel .equals (that .rel )
349
+ &&
350
+ (this .hreflang != null ? this .hreflang .equals (that .hreflang ) : this .hreflang == that .hreflang )
351
+ &&
352
+ (this .media != null ? this .media .equals (that .media ) : this .media == that .media )
353
+ &&
354
+ (this .title != null ? this .title .equals (that .title ) : this .title == that .title )
355
+ &&
356
+ (this .type != null ? this .type .equals (that .type ) : this .type == that .type )
357
+ &&
358
+ (this .deprecation != null ? this .deprecation .equals (that .deprecation ) : this .deprecation == that .deprecation );
216
359
}
217
360
218
361
/*
@@ -225,6 +368,21 @@ public int hashCode() {
225
368
int result = 17 ;
226
369
result += 31 * href .hashCode ();
227
370
result += 31 * rel .hashCode ();
371
+ if (hreflang != null ) {
372
+ result += 31 * hreflang .hashCode ();
373
+ }
374
+ if (media != null ) {
375
+ result += 31 * media .hashCode ();
376
+ }
377
+ if (title != null ) {
378
+ result += 31 * title .hashCode ();
379
+ }
380
+ if (type != null ) {
381
+ result += 31 * type .hashCode ();
382
+ }
383
+ if (deprecation != null ) {
384
+ result += 31 * deprecation .hashCode ();
385
+ }
228
386
return result ;
229
387
}
230
388
@@ -234,7 +392,29 @@ public int hashCode() {
234
392
*/
235
393
@ Override
236
394
public String toString () {
237
- return String .format ("<%s>;rel=\" %s\" " , href , rel );
395
+ String linkString = String .format ("<%s>;rel=\" %s\" " , href , rel );
396
+
397
+ if (hreflang != null ) {
398
+ linkString += ";hreflang=\" " + hreflang + "\" " ;
399
+ }
400
+
401
+ if (media != null ) {
402
+ linkString += ";media=\" " + media + "\" " ;
403
+ }
404
+
405
+ if (title != null ) {
406
+ linkString += ";title=\" " + title + "\" " ;
407
+ }
408
+
409
+ if (type != null ) {
410
+ linkString += ";type=\" " + type + "\" " ;
411
+ }
412
+
413
+ if (deprecation != null ) {
414
+ linkString += ";deprecation=\" " + deprecation + "\" " ;
415
+ }
416
+
417
+ return linkString ;
238
418
}
239
419
240
420
/**
@@ -263,7 +443,34 @@ public static Link valueOf(String element) {
263
443
throw new IllegalArgumentException ("Link does not provide a rel attribute!" );
264
444
}
265
445
266
- return new Link (matcher .group (1 ), attributes .get ("rel" ));
446
+ Set <String > unrecognizedHeaders = unrecognizedHeaders (attributes );
447
+ if (!unrecognizedHeaders .isEmpty ()) {
448
+ throw new IllegalArgumentException ("Link contains invalid RFC5988 headers! => " + unrecognizedHeaders );
449
+ }
450
+
451
+ Link link = new Link (matcher .group (1 ), attributes .get ("rel" ));
452
+
453
+ if (attributes .containsKey ("hreflang" )) {
454
+ link = link .withHreflang (attributes .get ("hreflang" ));
455
+ }
456
+
457
+ if (attributes .containsKey ("media" )) {
458
+ link = link .withMedia (attributes .get ("media" ));
459
+ }
460
+
461
+ if (attributes .containsKey ("title" )) {
462
+ link = link .withTitle (attributes .get ("title" ));
463
+ }
464
+
465
+ if (attributes .containsKey ("type" )) {
466
+ link = link .withType (attributes .get ("type" ));
467
+ }
468
+
469
+ if (attributes .containsKey ("deprecation" )) {
470
+ link = link .withDeprecation (attributes .get ("deprecation" ));
471
+ }
472
+
473
+ return link ;
267
474
268
475
} else {
269
476
throw new IllegalArgumentException (String .format ("Given link header %s is not RFC5988 compliant!" , element ));
@@ -283,7 +490,7 @@ private static Map<String, String> getAttributeMap(String source) {
283
490
}
284
491
285
492
Map <String , String > attributes = new HashMap <String , String >();
286
- Pattern keyAndValue = Pattern .compile ("(\\ w+)=\" (\\ p{Lower}[\\ p{Lower}\\ p{Digit}\\ .\\ -]*|" + URI_PATTERN + ")\" " );
493
+ Pattern keyAndValue = Pattern .compile ("(\\ w+)=\" (\\ p{Lower}[\\ p{Lower}\\ p{Digit}\\ .\\ -\\ s ]*|" + URI_PATTERN + ")\" " );
287
494
Matcher matcher = keyAndValue .matcher (source );
288
495
289
496
while (matcher .find ()) {
@@ -292,4 +499,22 @@ private static Map<String, String> getAttributeMap(String source) {
292
499
293
500
return attributes ;
294
501
}
502
+
503
+ /**
504
+ * Scan for any headers not recognized.
505
+ *
506
+ * @param attributes
507
+ * @return
508
+ */
509
+ private static Set <String > unrecognizedHeaders (final Map <String , String > attributes ) {
510
+
511
+ // Copy the existing keys to avoid damaging the original.
512
+ Set <String > unrecognizedHeaders = new HashSet <String >();
513
+ unrecognizedHeaders .addAll (attributes .keySet ());
514
+
515
+ // Remove all recognized headers
516
+ unrecognizedHeaders .removeAll (Arrays .asList ("href" , "rel" , "hreflang" , "media" , "title" , "type" , "deprecation" ));
517
+
518
+ return unrecognizedHeaders ;
519
+ }
295
520
}
0 commit comments