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,34 @@ 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
+ */
115
+ public Link (String href , String rel , String hreflang , String media , String title , String type , String deprecation ) {
116
+
117
+ this (href , rel );
118
+ this .hreflang = hreflang ;
119
+ this .media = media ;
120
+ this .title = title ;
121
+ this .type = type ;
122
+ this .deprecation = deprecation ;
123
+ }
124
+
96
125
/**
97
126
* Empty constructor required by the marshalling framework.
98
127
*/
@@ -118,6 +147,51 @@ public String getRel() {
118
147
return rel ;
119
148
}
120
149
150
+ /**
151
+ * Returns the hreflang of the link.
152
+ *
153
+ * @return
154
+ */
155
+ public String getHreflang () {
156
+ return hreflang ;
157
+ }
158
+
159
+ /**
160
+ * Returns the media of the link.
161
+ *
162
+ * @return
163
+ */
164
+ public String getMedia () {
165
+ return media ;
166
+ }
167
+
168
+ /**
169
+ * Returns the title of the link.
170
+ *
171
+ * @return
172
+ */
173
+ public String getTitle () {
174
+ return title ;
175
+ }
176
+
177
+ /**
178
+ * Returns the type of the link
179
+ *
180
+ * @return
181
+ */
182
+ public String getType () {
183
+ return type ;
184
+ }
185
+
186
+ /**
187
+ * Returns link about deprecation of this link.
188
+ *
189
+ * @return
190
+ */
191
+ public String getDeprecation () {
192
+ return deprecation ;
193
+ }
194
+
121
195
/**
122
196
* Returns a {@link Link} pointing to the same URI but with the given relation.
123
197
*
@@ -137,6 +211,61 @@ public Link withSelfRel() {
137
211
return withRel (Link .REL_SELF );
138
212
}
139
213
214
+ /**
215
+ * Returns a {@link Link} with the {@code hreflang} attribute filled out.
216
+ *
217
+ * @param hreflang
218
+ * @return
219
+ */
220
+ public Link withHreflang (String hreflang ) {
221
+ Assert .hasText (hreflang , "hreflang must not be null or empty!" );
222
+ return new Link (this .href , this .rel , hreflang , this .media , this .title , this .type , this .deprecation );
223
+ }
224
+
225
+ /**
226
+ * Returns a {@link Link} with the {@code media} attribute filled out.
227
+ *
228
+ * @param media
229
+ * @return
230
+ */
231
+ public Link withMedia (String media ) {
232
+ Assert .hasText (media , "media must not be null or empty!" );
233
+ return new Link (this .href , this .rel , this .hreflang , media , this .title , this .type , this .deprecation );
234
+ }
235
+
236
+ /**
237
+ * Returns a {@link Link} with the {@code title} attribute filled out.
238
+ *
239
+ * @param title
240
+ * @return
241
+ */
242
+ public Link withTitle (String title ) {
243
+ Assert .hasText (title , "title must not be null or empty!" );
244
+ return new Link (this .href , this .rel , this .hreflang , this .media , title , this .type , this .deprecation );
245
+ }
246
+
247
+ /**
248
+ * Returns a {@link Link} with the {@code type} attribute filled out.
249
+ *
250
+ * @param type
251
+ * @return
252
+ */
253
+ public Link withType (String type ) {
254
+ Assert .hasText (type , "type must not be null or empty!" );
255
+ return new Link (this .href , this .rel , this .hreflang , this .media , this .title , type , this .deprecation );
256
+ }
257
+
258
+ /**
259
+ * Returns a {@link Link} with the {@code deprecation} attribute filled out.
260
+ *
261
+ * @param deprecation
262
+ * @return
263
+ */
264
+ public Link withDeprecation (String deprecation ) {
265
+ Assert .hasText (deprecation , "deprecation must not be null or empty!" );
266
+ return new Link (this .href , this .rel , this .hreflang , this .media , this .title , this .type , deprecation );
267
+ }
268
+
140
269
/**
141
270
* Returns the variable names contained in the template.
142
271
*
@@ -212,7 +341,21 @@ public boolean equals(Object obj) {
212
341
213
342
Link that = (Link ) obj ;
214
343
215
- return this .href .equals (that .href ) && this .rel .equals (that .rel );
344
+ return
345
+ this .href .equals (that .href )
346
+ &&
347
+ this .rel .equals (that .rel )
348
+ &&
349
+ (this .hreflang != null ? this .hreflang .equals (that .hreflang ) : this .hreflang == that .hreflang )
350
+ &&
351
+ (this .media != null ? this .media .equals (that .media ) : this .media == that .media )
352
+ &&
353
+ (this .title != null ? this .title .equals (that .title ) : this .title == that .title )
354
+ &&
355
+ (this .type != null ? this .type .equals (that .type ) : this .type == that .type )
356
+ &&
357
+ (this .deprecation != null ? this .deprecation .equals (that .deprecation ) : this .deprecation == that .deprecation );
358
+
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,30 @@ 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 .toString () + "\" " ;
415
+ }
416
+
417
+
418
+ return linkString ;
238
419
}
239
420
240
421
/**
@@ -263,7 +444,33 @@ public static Link valueOf(String element) {
263
444
throw new IllegalArgumentException ("Link does not provide a rel attribute!" );
264
445
}
265
446
266
- return new Link (matcher .group (1 ), attributes .get ("rel" ));
447
+ if (!unrecognizedHeaders (attributes ).isEmpty ()) {
448
+ throw new IllegalArgumentException ("Link contains invalid RFC5988 headers!" );
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,23 @@ 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
+ addAll (attributes .keySet ());
514
+ }};
515
+
516
+ // Remove all recognized headers
517
+ unrecognizedHeaders .removeAll (Arrays .asList ("href" , "rel" , "hreflang" , "media" , "title" , "type" , "deprecation" ));
518
+
519
+ return unrecognizedHeaders ;
520
+ }
295
521
}
0 commit comments