Skip to content

Commit 9d097a3

Browse files
Migrate UUID mapping to ExtendedType
Supports binary mapping
1 parent 4702c36 commit 9d097a3

8 files changed

Lines changed: 345 additions & 2 deletions

File tree

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
/*****************************************************************
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
****************************************************************/
19+
20+
package org.apache.cayenne.access.types;
21+
22+
import java.nio.ByteBuffer;
23+
import java.sql.Blob;
24+
import java.sql.CallableStatement;
25+
import java.sql.PreparedStatement;
26+
import java.sql.ResultSet;
27+
import java.sql.SQLException;
28+
import java.sql.Types;
29+
import java.util.UUID;
30+
31+
import org.apache.cayenne.CayenneRuntimeException;
32+
33+
/**
34+
* ExtendedType for <code>java.util.UUID</code> that supports string and binary JDBC columns. <br>
35+
* For char columns, stores UUID as canonical string. <br>
36+
* For binary columns, stores UUID in 16-byte big-endian form. <br>
37+
* For other columns (e.g. PostgreSQL native UUID), delegates to driver's native handling where possible.
38+
*
39+
* @since 4.2
40+
*/
41+
public class UUIDType implements ExtendedType<UUID> {
42+
43+
private static final int UUID_BYTES = 2 * Long.BYTES;
44+
45+
@Override
46+
public String getClassName() {
47+
return UUID.class.getName();
48+
}
49+
50+
@Override
51+
public UUID materializeObject(ResultSet rs, int index, int type) throws Exception {
52+
switch (type) {
53+
case Types.CHAR:
54+
case Types.VARCHAR:
55+
case Types.LONGVARCHAR: {
56+
String s = rs.getString(index);
57+
return s != null ? UUID.fromString(s) : null;
58+
}
59+
case Types.BINARY:
60+
case Types.VARBINARY:
61+
case Types.LONGVARBINARY: {
62+
byte[] b = rs.getBytes(index);
63+
return b != null ? fromBytes(b) : null;
64+
}
65+
case Types.BLOB: {
66+
return fromBlob(rs.getBlob(index));
67+
}
68+
default: {
69+
return fromObject(rs.getObject(index));
70+
}
71+
}
72+
}
73+
74+
@Override
75+
public UUID materializeObject(CallableStatement cs, int index, int type) throws Exception {
76+
switch (type) {
77+
case Types.CHAR:
78+
case Types.VARCHAR:
79+
case Types.LONGVARCHAR: {
80+
String s = cs.getString(index);
81+
return s != null ? UUID.fromString(s) : null;
82+
}
83+
case Types.BINARY:
84+
case Types.VARBINARY:
85+
case Types.LONGVARBINARY: {
86+
byte[] b = cs.getBytes(index);
87+
return b != null ? fromBytes(b) : null;
88+
}
89+
case Types.BLOB: {
90+
return fromBlob(cs.getBlob(index));
91+
}
92+
default: {
93+
Object o = cs.getObject(index);
94+
return fromObject(o);
95+
}
96+
}
97+
}
98+
99+
@Override
100+
public void setJdbcObject(PreparedStatement statement, UUID value, int pos, int type, int precision) throws Exception {
101+
if (value == null) {
102+
statement.setNull(pos, type);
103+
return;
104+
}
105+
106+
switch (type) {
107+
case Types.CHAR:
108+
case Types.VARCHAR:
109+
case Types.LONGVARCHAR: {
110+
statement.setString(pos, value.toString());
111+
return;
112+
}
113+
case Types.BINARY:
114+
case Types.VARBINARY:
115+
case Types.LONGVARBINARY: {
116+
statement.setBytes(pos, toBytes(value));
117+
return;
118+
}
119+
case Types.BLOB: {
120+
byte[] b = toBytes(value);
121+
if (precision != -1) {
122+
statement.setObject(pos, b, type, precision);
123+
} else {
124+
statement.setObject(pos, b, type);
125+
}
126+
return;
127+
}
128+
case Types.OTHER: {
129+
// native UUID
130+
statement.setObject(pos, value);
131+
return;
132+
}
133+
default: {
134+
if (precision != -1) {
135+
statement.setObject(pos, value.toString(), type, precision);
136+
} else {
137+
statement.setObject(pos, value.toString(), type);
138+
}
139+
}
140+
}
141+
}
142+
143+
@Override
144+
public String toString(UUID value) {
145+
return value == null ? "NULL" : value.toString();
146+
}
147+
148+
private static UUID fromBlob(Blob blob) throws SQLException {
149+
if (blob == null) {
150+
return null;
151+
}
152+
long len = blob.length();
153+
if (len == 0) {
154+
return null;
155+
}
156+
if (len != UUID_BYTES) {
157+
invalidUuidLength(len);
158+
}
159+
byte[] b = blob.getBytes(1, (int) len);
160+
return fromBytes(b);
161+
}
162+
163+
private static UUID fromObject(Object o) {
164+
if (o == null) {
165+
return null;
166+
}
167+
if (o instanceof UUID) {
168+
return (UUID) o;
169+
}
170+
if (o instanceof byte[]) {
171+
return fromBytes((byte[]) o);
172+
}
173+
return UUID.fromString(o.toString());
174+
}
175+
176+
private static UUID fromBytes(byte[] bytes) {
177+
if (bytes == null) {
178+
return null;
179+
}
180+
if (bytes.length != UUID_BYTES) {
181+
invalidUuidLength(bytes.length);
182+
}
183+
ByteBuffer bb = ByteBuffer.wrap(bytes);
184+
return new UUID(bb.getLong(), bb.getLong());
185+
}
186+
187+
private static byte[] toBytes(UUID uuid) {
188+
if (uuid == null) {
189+
return null;
190+
}
191+
return ByteBuffer.allocate(Long.BYTES * 2)
192+
.putLong(uuid.getMostSignificantBits())
193+
.putLong(uuid.getLeastSignificantBits())
194+
.array();
195+
}
196+
197+
private static void invalidUuidLength(long length) {
198+
throw new CayenneRuntimeException("Invalid UUID length, expected " + UUID_BYTES + " bytes: " + length);
199+
}
200+
}

cayenne/src/main/java/org/apache/cayenne/access/types/UUIDValueType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
/**
2727
* @since 4.0
28+
* @deprecated since 4.2, use {@link UUIDType} instead
2829
*/
2930
public class UUIDValueType implements ValueObjectType<UUID, String> {
3031

cayenne/src/main/java/org/apache/cayenne/configuration/runtime/CoreModule.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@ public void configure(Binder binder) {
394394
.addDefaultExtendedType(new UtilDateType())
395395
.addDefaultExtendedType(new CalendarType<>(GregorianCalendar.class))
396396
.addDefaultExtendedType(new CalendarType<>(Calendar.class))
397+
.addDefaultExtendedType(new UUIDType())
397398
.addDefaultExtendedType(new GeoJsonType())
398399
.addDefaultExtendedType(new WktType())
399400

@@ -402,7 +403,6 @@ public void configure(Binder binder) {
402403
// ValueObjectTypes
403404
.addValueObjectType(BigIntegerValueType.class)
404405
.addValueObjectType(BigDecimalValueType.class)
405-
.addValueObjectType(UUIDValueType.class)
406406
.addValueObjectType(LocalDateValueType.class)
407407
.addValueObjectType(LocalTimeValueType.class)
408408
.addValueObjectType(LocalDateTimeValueType.class)

cayenne/src/test/java/org/apache/cayenne/access/UUIDIT.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
****************************************************************/
1919
package org.apache.cayenne.access;
2020

21+
import java.nio.ByteBuffer;
2122
import java.util.UUID;
2223

2324
import org.apache.cayenne.Cayenne;
@@ -28,6 +29,7 @@
2829
import org.apache.cayenne.query.ObjectSelect;
2930
import org.apache.cayenne.test.jdbc.DBHelper;
3031
import org.apache.cayenne.test.jdbc.TableHelper;
32+
import org.apache.cayenne.testdo.uuid.UuidBinTestEntity;
3133
import org.apache.cayenne.testdo.uuid.UuidPkEntity;
3234
import org.apache.cayenne.testdo.uuid.UuidTestEntity;
3335
import org.apache.cayenne.unit.di.runtime.CayenneProjects;
@@ -49,10 +51,12 @@ public class UUIDIT extends RuntimeCase {
4951
private DBHelper dbHelper;
5052

5153
private TableHelper uuidPkEntity;
54+
private TableHelper uuidBinTable;
5255

5356
@Before
5457
public void setUp() throws Exception {
5558
uuidPkEntity = new TableHelper(dbHelper, "UUID_PK_ENTITY", "ID");
59+
uuidBinTable = new TableHelper(dbHelper, "UUID_BIN_TEST");
5660
}
5761

5862
@Test
@@ -116,4 +120,32 @@ public void testUUIDColumnSelect() throws Exception {
116120

117121
assertEquals(id, readValue2);
118122
}
123+
124+
@Test
125+
public void testUUIDBinary_InsertSelect() {
126+
UuidBinTestEntity test = context.newObject(UuidBinTestEntity.class);
127+
UUID expected = UUID.randomUUID();
128+
test.setUuid(expected);
129+
context.commitChanges();
130+
131+
UuidBinTestEntity testRead = ObjectSelect.query(UuidBinTestEntity.class).selectFirst(context);
132+
assertNotNull(testRead.getUuid());
133+
assertEquals(expected, testRead.getUuid());
134+
}
135+
136+
@Test
137+
public void testUUIDBinary_RawBytes() throws Exception {
138+
UuidBinTestEntity test = context.newObject(UuidBinTestEntity.class);
139+
UUID expected = UUID.randomUUID();
140+
test.setUuid(expected);
141+
context.commitChanges();
142+
143+
byte[] raw = uuidBinTable.getBytes("UUID");
144+
assertNotNull(raw);
145+
assertEquals(16, raw.length);
146+
147+
ByteBuffer bb = ByteBuffer.wrap(raw);
148+
UUID actual = new UUID(bb.getLong(), bb.getLong());
149+
assertEquals(expected, actual);
150+
}
119151
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package org.apache.cayenne.testdo.uuid;
2+
3+
import org.apache.cayenne.testdo.uuid.auto._UuidBinTestEntity;
4+
5+
public class UuidBinTestEntity extends _UuidBinTestEntity {
6+
7+
private static final long serialVersionUID = 1L;
8+
9+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package org.apache.cayenne.testdo.uuid.auto;
2+
3+
import java.io.IOException;
4+
import java.io.ObjectInputStream;
5+
import java.io.ObjectOutputStream;
6+
import java.util.UUID;
7+
8+
import org.apache.cayenne.PersistentObject;
9+
import org.apache.cayenne.exp.property.BaseProperty;
10+
import org.apache.cayenne.exp.property.NumericIdProperty;
11+
import org.apache.cayenne.exp.property.PropertyFactory;
12+
import org.apache.cayenne.exp.property.SelfProperty;
13+
import org.apache.cayenne.testdo.uuid.UuidBinTestEntity;
14+
15+
/**
16+
* Class _UuidBinTestEntity was generated by Cayenne.
17+
* It is probably a good idea to avoid changing this class manually,
18+
* since it may be overwritten next time code is regenerated.
19+
* If you need to make any customizations, please use subclass.
20+
*/
21+
public abstract class _UuidBinTestEntity extends PersistentObject {
22+
23+
private static final long serialVersionUID = 1L;
24+
25+
public static final SelfProperty<UuidBinTestEntity> SELF = PropertyFactory.createSelf(UuidBinTestEntity.class);
26+
27+
public static final NumericIdProperty<Integer> ID_PK_PROPERTY = PropertyFactory.createNumericId("ID", "UuidBinTestEntity", Integer.class);
28+
public static final String ID_PK_COLUMN = "ID";
29+
30+
public static final BaseProperty<UUID> UUID = PropertyFactory.createBase("uuid", UUID.class);
31+
32+
protected UUID uuid;
33+
34+
35+
public void setUuid(UUID uuid) {
36+
beforePropertyWrite("uuid", this.uuid, uuid);
37+
this.uuid = uuid;
38+
}
39+
40+
public UUID getUuid() {
41+
beforePropertyRead("uuid");
42+
return this.uuid;
43+
}
44+
45+
@Override
46+
public Object readPropertyDirectly(String propName) {
47+
if(propName == null) {
48+
throw new IllegalArgumentException();
49+
}
50+
51+
switch(propName) {
52+
case "uuid":
53+
return this.uuid;
54+
default:
55+
return super.readPropertyDirectly(propName);
56+
}
57+
}
58+
59+
@Override
60+
public void writePropertyDirectly(String propName, Object val) {
61+
if(propName == null) {
62+
throw new IllegalArgumentException();
63+
}
64+
65+
switch (propName) {
66+
case "uuid":
67+
this.uuid = (UUID)val;
68+
break;
69+
default:
70+
super.writePropertyDirectly(propName, val);
71+
}
72+
}
73+
74+
private void writeObject(ObjectOutputStream out) throws IOException {
75+
writeSerialized(out);
76+
}
77+
78+
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
79+
readSerialized(in);
80+
}
81+
82+
@Override
83+
protected void writeState(ObjectOutputStream out) throws IOException {
84+
super.writeState(out);
85+
out.writeObject(this.uuid);
86+
}
87+
88+
@Override
89+
protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
90+
super.readState(in);
91+
this.uuid = (UUID)in.readObject();
92+
}
93+
94+
}

0 commit comments

Comments
 (0)