1
1
package org .molgenis .emx2 .rdf ;
2
2
3
+ import static java .util .stream .Collectors .toCollection ;
3
4
import static org .molgenis .emx2 .FilterBean .and ;
4
5
import static org .molgenis .emx2 .FilterBean .f ;
5
6
import static org .molgenis .emx2 .Operator .EQUALS ;
6
7
import static org .molgenis .emx2 .rdf .IriGenerator .escaper ;
8
+ import static org .molgenis .emx2 .utils .TypeUtils .convertToCamelCase ;
7
9
8
10
import java .net .URLDecoder ;
9
11
import java .nio .charset .StandardCharsets ;
10
12
import java .util .*;
13
+ import org .molgenis .emx2 .Column ;
11
14
import org .molgenis .emx2 .Filter ;
12
15
import org .molgenis .emx2 .MolgenisException ;
16
+ import org .molgenis .emx2 .Reference ;
17
+ import org .molgenis .emx2 .Row ;
18
+ import org .molgenis .emx2 .Table ;
19
+ import org .molgenis .emx2 .TableMetadata ;
13
20
14
21
/**
15
22
* Does not support "{@code _ARRAY}" {@link org.molgenis.emx2.ColumnType}'s
20
27
class PrimaryKey {
21
28
public static final String NAME_VALUE_SEPARATOR = "=" ;
22
29
public static final String KEY_PARTS_SEPARATOR = "&" ;
23
- private final Map <String , String > keys ;
30
+ // "column name", "column value" (non-escaped due to getFilter() functionality)
31
+ private final SortedMap <String , String > keys ;
24
32
25
- // use map instead of list<NameValuePair> to prevent duplicate entries
26
- // some foreign key have overlapping relationships which resulted in a bug
33
+ /**
34
+ * A table should always have a key=1 column (and it must have a value), so validating on empty
35
+ * values should not be needed.
36
+ */
37
+ static PrimaryKey fromRow (TableMetadata table , Row row ) {
38
+ final SortedMap <String , String > keyParts = new TreeMap <>();
39
+ for (final Column column : table .getPrimaryKeyColumns ()) {
40
+ if (column .isReference ()) {
41
+ for (final Reference reference : column .getReferences ()) {
42
+ final String [] values = row .getStringArray (reference .getName ());
43
+ for (final String value : values ) {
44
+ keyParts .put (reference .getName (), value );
45
+ }
46
+ }
47
+ } else {
48
+ keyParts .put (column .getName (), row .getString (column .getName ()));
49
+ }
50
+ }
51
+ return new PrimaryKey (keyParts );
52
+ }
27
53
28
- static PrimaryKey makePrimaryKeyFromEncodedKey (String encodedValue ) {
29
- String [] encodedPairs = encodedValue .split (KEY_PARTS_SEPARATOR );
54
+ static PrimaryKey fromRow (Table table , Row row ) {
55
+ return fromRow (table .getMetadata (), row );
56
+ }
57
+
58
+ /**
59
+ * Uses map instead of {@code list<NameValuePair>} to prevent duplicate entries as some foreign
60
+ * key have overlapping relationships which resulted in a bug.
61
+ *
62
+ * @throws IllegalArgumentException if encodedString contains 0 pairs, encodedString is unsorted
63
+ */
64
+ static PrimaryKey fromEncodedString (TableMetadata table , String encodedString ) {
65
+ String [] encodedPairs = encodedString .split (KEY_PARTS_SEPARATOR );
30
66
if (encodedPairs .length == 0 ) {
31
67
throw new IllegalArgumentException ("There must be at least one key." );
68
+ } else if (encodedPairs .length > 1
69
+ && !Arrays .equals (Arrays .stream (encodedPairs ).sorted ().toArray (), encodedPairs )) {
70
+ throw new IllegalArgumentException ("The encoded String does not contain sorted values" );
32
71
} else {
33
- Map <String , String > pairs = new LinkedHashMap <>();
72
+ SortedMap <String , String > pairs = new TreeMap <>();
34
73
for (var pair : encodedPairs ) {
35
74
var parts = pair .split (NAME_VALUE_SEPARATOR );
36
75
if (parts .length != 2 ) {
37
76
throw new IllegalArgumentException (
38
77
"Can't decode the key, name value pair is incomplete." );
39
78
}
40
- var name = URLDecoder .decode (parts [0 ], StandardCharsets .UTF_8 );
41
- var value = URLDecoder .decode (parts [1 ], StandardCharsets .UTF_8 );
79
+ String identifier = URLDecoder .decode (parts [0 ], StandardCharsets .UTF_8 );
80
+ String name =
81
+ recursiveIdentifierToName (
82
+ table ,
83
+ Arrays .stream (identifier .split ("\\ ." )).collect (toCollection (ArrayList ::new )));
84
+ String value = URLDecoder .decode (parts [1 ], StandardCharsets .UTF_8 );
42
85
pairs .put (name , value );
43
86
}
44
87
return new PrimaryKey (pairs );
45
88
}
46
89
}
47
90
48
- PrimaryKey (Map <String , String > keys ) {
91
+ private static String recursiveIdentifierToName (TableMetadata table , List <String > remaining ) {
92
+ String currentIdentifier = remaining .remove (0 );
93
+ Column currentColumn = table .getColumnByIdentifier (currentIdentifier );
94
+ if (currentColumn == null ) {
95
+ throw new IllegalArgumentException (
96
+ "Could not find (inherited) column for identifier \" "
97
+ + currentIdentifier
98
+ + "\" in table \" "
99
+ + table .getTableName ()
100
+ + "\" " );
101
+ }
102
+
103
+ if (!remaining .isEmpty ()) {
104
+ return currentColumn .getName ()
105
+ + "."
106
+ + recursiveIdentifierToName (currentColumn .getRefTable (), remaining );
107
+ }
108
+ return currentColumn .getName ();
109
+ }
110
+
111
+ static PrimaryKey fromEncodedString (Table table , String encodedValue ) {
112
+ return fromEncodedString (table .getMetadata (), encodedValue );
113
+ }
114
+
115
+ PrimaryKey (SortedMap <String , String > keys ) {
49
116
if (keys .isEmpty ()) {
50
117
throw new IllegalArgumentException ("There must be at least one key." );
118
+ } else if (keys .containsValue (null )) {
119
+ throw new IllegalArgumentException ("Values are not allowed to be null." );
51
120
}
52
121
this .keys = keys ;
53
122
}
54
123
55
- String getEncodedValue () {
124
+ PrimaryKey (Map <String , String > keys ) {
125
+ this (new TreeMap <>(keys ));
126
+ }
127
+
128
+ String getEncodedString () {
56
129
try {
57
130
List <String > encodedPairs = new ArrayList <>();
58
- // Sort the list to have a stable order
59
- var sortedMap = new TreeMap <>(this .keys );
60
- for (var pair : sortedMap .entrySet ()) {
61
- var name = escaper .escape (pair .getKey ());
131
+ for (var pair : keys .entrySet ()) {
132
+ var name = escaper .escape (convertToCamelCase (pair .getKey ()));
62
133
var value = escaper .escape (pair .getValue ());
63
134
encodedPairs .add (name + NAME_VALUE_SEPARATOR + value );
64
135
}
@@ -74,7 +145,7 @@ Filter getFilter() {
74
145
return and (filters );
75
146
}
76
147
77
- Map <String , String > getKeys () {
148
+ SortedMap <String , String > getKeys () {
78
149
return keys ;
79
150
}
80
151
}
0 commit comments