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,66 @@ 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
+
223
+ Assert .hasText (hreflang , "hreflang must not be null or empty!" );
224
+ return new Link (this .href , this .rel , hreflang , this .media , this .title , this .type , this .deprecation );
225
+ }
226
+
227
+ /**
228
+ * Returns a {@link Link} with the {@code media} attribute filled out.
229
+ *
230
+ * @param media
231
+ * @return
232
+ */
233
+ public Link withMedia (String media ) {
234
+
235
+ Assert .hasText (media , "media must not be null or empty!" );
236
+ return new Link (this .href , this .rel , this .hreflang , media , this .title , this .type , this .deprecation );
237
+ }
238
+
239
+ /**
240
+ * Returns a {@link Link} with the {@code title} attribute filled out.
241
+ *
242
+ * @param title
243
+ * @return
244
+ */
245
+ public Link withTitle (String title ) {
246
+
247
+ Assert .hasText (title , "title must not be null or empty!" );
248
+ return new Link (this .href , this .rel , this .hreflang , this .media , title , this .type , this .deprecation );
249
+ }
250
+
251
+ /**
252
+ * Returns a {@link Link} with the {@code type} attribute filled out.
253
+ *
254
+ * @param type
255
+ * @return
256
+ */
257
+ public Link withType (String type ) {
258
+
259
+ Assert .hasText (type , "type must not be null or empty!" );
260
+ return new Link (this .href , this .rel , this .hreflang , this .media , this .title , type , this .deprecation );
261
+ }
262
+
263
+ /**
264
+ * Returns a {@link Link} with the {@code deprecation} attribute filled out.
265
+ *
266
+ * @param deprecation
267
+ * @return
268
+ */
269
+ public Link withDeprecation (String deprecation ) {
270
+
271
+ Assert .hasText (deprecation , "deprecation must not be null or empty!" );
272
+ return new Link (this .href , this .rel , this .hreflang , this .media , this .title , this .type , deprecation );
273
+ }
274
+
140
275
/**
141
276
* Returns the variable names contained in the template.
142
277
*
@@ -212,7 +347,20 @@ public boolean equals(Object obj) {
212
347
213
348
Link that = (Link ) obj ;
214
349
215
- return this .href .equals (that .href ) && this .rel .equals (that .rel );
350
+ return
351
+ this .href .equals (that .href )
352
+ &&
353
+ this .rel .equals (that .rel )
354
+ &&
355
+ (this .hreflang != null ? this .hreflang .equals (that .hreflang ) : this .hreflang == that .hreflang )
356
+ &&
357
+ (this .media != null ? this .media .equals (that .media ) : this .media == that .media )
358
+ &&
359
+ (this .title != null ? this .title .equals (that .title ) : this .title == that .title )
360
+ &&
361
+ (this .type != null ? this .type .equals (that .type ) : this .type == that .type )
362
+ &&
363
+ (this .deprecation != null ? this .deprecation .equals (that .deprecation ) : this .deprecation == that .deprecation );
216
364
}
217
365
218
366
/*
@@ -225,6 +373,21 @@ public int hashCode() {
225
373
int result = 17 ;
226
374
result += 31 * href .hashCode ();
227
375
result += 31 * rel .hashCode ();
376
+ if (hreflang != null ) {
377
+ result += 31 * hreflang .hashCode ();
378
+ }
379
+ if (media != null ) {
380
+ result += 31 * media .hashCode ();
381
+ }
382
+ if (title != null ) {
383
+ result += 31 * title .hashCode ();
384
+ }
385
+ if (type != null ) {
386
+ result += 31 * type .hashCode ();
387
+ }
388
+ if (deprecation != null ) {
389
+ result += 31 * deprecation .hashCode ();
390
+ }
228
391
return result ;
229
392
}
230
393
@@ -234,7 +397,29 @@ public int hashCode() {
234
397
*/
235
398
@ Override
236
399
public String toString () {
237
- return String .format ("<%s>;rel=\" %s\" " , href , rel );
400
+ String linkString = String .format ("<%s>;rel=\" %s\" " , href , rel );
401
+
402
+ if (hreflang != null ) {
403
+ linkString += ";hreflang=\" " + hreflang + "\" " ;
404
+ }
405
+
406
+ if (media != null ) {
407
+ linkString += ";media=\" " + media + "\" " ;
408
+ }
409
+
410
+ if (title != null ) {
411
+ linkString += ";title=\" " + title + "\" " ;
412
+ }
413
+
414
+ if (type != null ) {
415
+ linkString += ";type=\" " + type + "\" " ;
416
+ }
417
+
418
+ if (deprecation != null ) {
419
+ linkString += ";deprecation=\" " + deprecation + "\" " ;
420
+ }
421
+
422
+ return linkString ;
238
423
}
239
424
240
425
/**
@@ -263,7 +448,34 @@ public static Link valueOf(String element) {
263
448
throw new IllegalArgumentException ("Link does not provide a rel attribute!" );
264
449
}
265
450
266
- return new Link (matcher .group (1 ), attributes .get ("rel" ));
451
+ Set <String > unrecognizedHeaders = unrecognizedHeaders (attributes );
452
+ if (!unrecognizedHeaders .isEmpty ()) {
453
+ throw new IllegalArgumentException ("Link contains invalid RFC5988 headers! => " + unrecognizedHeaders );
454
+ }
455
+
456
+ Link link = new Link (matcher .group (1 ), attributes .get ("rel" ));
457
+
458
+ if (attributes .containsKey ("hreflang" )) {
459
+ link = link .withHreflang (attributes .get ("hreflang" ));
460
+ }
461
+
462
+ if (attributes .containsKey ("media" )) {
463
+ link = link .withMedia (attributes .get ("media" ));
464
+ }
465
+
466
+ if (attributes .containsKey ("title" )) {
467
+ link = link .withTitle (attributes .get ("title" ));
468
+ }
469
+
470
+ if (attributes .containsKey ("type" )) {
471
+ link = link .withType (attributes .get ("type" ));
472
+ }
473
+
474
+ if (attributes .containsKey ("deprecation" )) {
475
+ link = link .withDeprecation (attributes .get ("deprecation" ));
476
+ }
477
+
478
+ return link ;
267
479
268
480
} else {
269
481
throw new IllegalArgumentException (String .format ("Given link header %s is not RFC5988 compliant!" , element ));
@@ -283,7 +495,7 @@ private static Map<String, String> getAttributeMap(String source) {
283
495
}
284
496
285
497
Map <String , String > attributes = new HashMap <String , String >();
286
- Pattern keyAndValue = Pattern .compile ("(\\ w+)=\" (\\ p{Lower}[\\ p{Lower}\\ p{Digit}\\ .\\ -]*|" + URI_PATTERN + ")\" " );
498
+ Pattern keyAndValue = Pattern .compile ("(\\ w+)=\" (\\ p{Lower}[\\ p{Lower}\\ p{Digit}\\ .\\ -\\ s ]*|" + URI_PATTERN + ")\" " );
287
499
Matcher matcher = keyAndValue .matcher (source );
288
500
289
501
while (matcher .find ()) {
@@ -292,4 +504,22 @@ private static Map<String, String> getAttributeMap(String source) {
292
504
293
505
return attributes ;
294
506
}
507
+
508
+ /**
509
+ * Scan for any headers not recognized.
510
+ *
511
+ * @param attributes
512
+ * @return
513
+ */
514
+ private static Set <String > unrecognizedHeaders (final Map <String , String > attributes ) {
515
+
516
+ // Copy the existing keys to avoid damaging the original.
517
+ Set <String > unrecognizedHeaders = new HashSet <String >();
518
+ unrecognizedHeaders .addAll (attributes .keySet ());
519
+
520
+ // Remove all recognized headers
521
+ unrecognizedHeaders .removeAll (Arrays .asList ("href" , "rel" , "hreflang" , "media" , "title" , "type" , "deprecation" ));
522
+
523
+ return unrecognizedHeaders ;
524
+ }
295
525
}
0 commit comments