Skip to content

Commit 894d06c

Browse files
authored
Rewrite for UUID v4
1. Completely rewrote UUID generation to comply with UUID v4 standard 2. Renamed project & class from Guid to Uuid (since UUID is the true standard) 3. Added package.xml and Travis CI builds 4. Rewrote README.md to include use cases & code samples
1 parent c4768ee commit 894d06c

File tree

10 files changed

+303
-87
lines changed

10 files changed

+303
-87
lines changed

.gitignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#Folders to exclude
2+
.settings/
3+
config/
4+
debug/
5+
deploy/
6+
7+
#Files to exclude
8+
*.log
9+
*.sublime-project
10+
*.sublime-workspace
11+
*.sublime-settings
12+

.travis.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
language: node_js
2+
node_js:
3+
- "7"
4+
install:
5+
- npm install -g jsforce-metadata-tools
6+
script:
7+
- jsforce-deploy --checkOnly -u $DEPLOYMENT_USERNAME -p $DEPLOYMENT_PASSWORD$DEPLOYMENT_TOKEN -D $TRAVIS_BUILD_DIR/src -l $DEPLOYMENT_LOGIN_URL --rollbackOnError true --testLevel $DEPLOYMENT_TEST_LEVEL --pollTimeout $POLL_TIMEOUT --pollInterval $POLL_INTERVAL--verbose

README.md

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,75 @@
1-
# Apex GUID
2-
<a href="https://githubsfdeploy.herokuapp.com?owner=jongpie&repo=apexguid">
3-
<img alt="Deploy to Salesforce"
4-
src="https://raw.githubusercontent.com/afawcett/githubsfdeploy/master/src/main/webapp/resources/img/deploy.png">
1+
# Apex UUID
2+
[![Build Status](https://travis-ci.org/jongpie/ApexUuid.svg?branch=master)](https://travis-ci.org/jongpie/ApexUuid)
3+
4+
<a href="https://githubsfdeploy.herokuapp.com" target="_blank">
5+
<img alt="Deploy to Salesforce" src="https://raw.githubusercontent.com/afawcett/githubsfdeploy/master/deploy.png">
56
</a>
6-
## Issue
7-
Often times, we need be able to refer to related records before they have been inserted. Salesforce does not provide a way to generate a unique ID in Apex, such as as GUID/UUID. This repo aims to solve that.
87

9-
## Goal
10-
* Create a solution that allows developers to generate a unique ID in code that can be used as an external ID text field
8+
## Usage
9+
Provides a way to generate a [UUID (Universally Unique Identifier)](https://en.wikipedia.org/wiki/Universally_unique_identifier) in Salesforce's Apex language. This uses Verion 4 of the UUID standard - more details available [here](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_(random))
10+
11+
### Generating & using a UUID
12+
To generate a UUID, simply instantiate a new instance. The string value can be retrieved with `getValue()`
13+
```
14+
Uuid myUuid = new Uuid();
15+
String myUuidValue = myUuid.getValue();
16+
```
17+
18+
You can use the UUID value as a unique ID, generated by Apex. This lets you do some powerful things in Apex when your objects have external ID fields to store the UUID value.
19+
20+
It's best if you create a custom field on your object to store the UUID value with these properties: Text(36) (External ID) (Unique Case Insensitive).
21+
22+
In the code samples below, the field name Uuid__c is used as an example field name.
23+
24+
> ##### Example: Using a code-generated external ID (such as a UUID), we can create multiple accounts and related contacts with only 1 DML statement
25+
26+
```
27+
List<Sobject> recordsToCreate = new List<Sobject>();
28+
// Create 10 sample accounts
29+
for(Integer accountCount = 0; accountCount < 10; accountCount++) {
30+
// Create a new account & set a custom external ID text field called Uuid__c
31+
Account newAccount = new Account(
32+
Name = 'Account ' + accountCount,
33+
Uuid__c = new Uuid().getValue()
34+
);
35+
recordsToCreate.add(newAccount);
36+
37+
// For each sample account, create 10 sample contacts
38+
for(Integer contactCount = 0; contactCount < 10; contactCount++) {
39+
// Instead of setting contact.AccountId with a Salesforce ID...
40+
// we can use an Account object with a Uuid__c value to set the Contact-Account relationship
41+
Contact newContact = new Contact(
42+
Account = new Account(Uuid__c = newAccount.Uuid__c),
43+
LastName = 'Contact ' + contactCount
44+
);
45+
recordsToCreate.add(newContact);
46+
}
47+
}
48+
// Sort so that the accounts are created before contacts (accounts are the parent object)
49+
recordsToCreate.sort();
50+
insert recordsToCreate;
51+
52+
// Verify that we only used 1 DML statement
53+
System.assertEquals(1, Limits.getDmlStatements());
54+
```
55+
56+
### Using a UUID's string value
57+
If you already have a UUID as a string (previously generated in Apex, generated by an external system, etc), there are 3 static methods to help work with the string value.
58+
1. **Do I have a valid UUID value?**
1159

12-
## Implementation
13-
--Coming soon--
60+
This checks if the string value matches the regex for a UUID v4, including hyphens (but it is case-insensitive)
61+
```
62+
Boolean isValid = Uuid.isValid(myUuidValue);
63+
```
64+
2. **I have a UUID value but need to format it**
1465
15-
### Example Implementation: --Coming soon--
66+
This returns a formatted string that follows the UUID pattern 8-4-4-4-12 in lowercase. If an invalid string is provided, a UuidException is thrown.
67+
```
68+
String formattedUuidValue = Uuid.formatValue(myUnformattedUuidValue);
69+
```
70+
3. **I have a UUID value, how can I use it to construct a UUID?**
1671
72+
This will automatically format the value for you, but the intial value must be a valid (unformatted) UUID string
73+
```
74+
Uuid myUuid = Uuid.valueOf(myUuidValue);
75+
```

src/classes/Guid.cls

Lines changed: 0 additions & 27 deletions
This file was deleted.

src/classes/Guid_Tests.cls

Lines changed: 0 additions & 47 deletions
This file was deleted.

src/classes/Uuid.cls

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/******************************************************************************************
2+
* This file is part of the Apex UUID project, released under the MIT License. *
3+
* See LICENSE file or go to https://github.com/jongpie/ApexUuid for full license details. *
4+
******************************************************************************************/
5+
public without sharing class Uuid {
6+
7+
private static final Integer HEX_BASE = HEX_CHARACTERS.length();
8+
private static final String HEX_CHARACTERS = '0123456789abcdef';
9+
private static final String HEX_PREFIX = '0x';
10+
private static final List<String> HEX_CHARACTER_LIST = HEX_CHARACTERS.split('');
11+
private static final Integer UUID_V4_LENGTH = 36;
12+
private static final String UUID_V4_REGEX = '[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}';
13+
14+
public static String formatValue(String unformattedValue) {
15+
final String invalidValueError = unformattedValue + ' is not a valid UUID value';
16+
17+
// Remove any non-alphanumeric characters
18+
unformattedValue = unformattedValue.replaceAll('[^a-zA-Z0-9]', '');
19+
20+
// If the unformatted value isn't even the right length to be valid, then throw an exception
21+
// Subtract 4 because the UUID_V4_LENGTH includes 4 '-' characters in the UUID pattern
22+
if(unformattedValue.length() != (UUID_V4_LENGTH - 4)) throw new UuidException(invalidValueError);
23+
24+
// UUID Pattern: 8-4-4-4-12
25+
String formattedValue = unformattedValue.substring(0, 8)
26+
+ '-' + unformattedValue.substring(8, 12)
27+
+ '-' + unformattedValue.substring(12, 16)
28+
+ '-' + unformattedValue.substring(16, 20)
29+
+ '-' + unformattedValue.substring(20);
30+
31+
formattedValue = formattedValue.toLowerCase();
32+
33+
if(!Uuid.isValid(formattedValue)) throw new UuidException(invalidValueError);
34+
35+
return formattedValue;
36+
}
37+
38+
public static Boolean isValid(String uuidValue) {
39+
if(String.isBlank(uuidValue)) return false;
40+
if(uuidValue.length() != UUID_V4_LENGTH) return false;
41+
42+
Pattern uuidPattern = Pattern.compile(UUID_V4_REGEX.toLowerCase());
43+
Matcher uuidMatcher = uuidPattern.matcher(uuidValue.toLowerCase());
44+
45+
return uuidMatcher.matches();
46+
}
47+
48+
public static Uuid valueOf(String uuidValue) {
49+
return new Uuid(uuidValue);
50+
}
51+
52+
private final String value;
53+
54+
public Uuid() {
55+
this.value = this.generateValue();
56+
}
57+
58+
private Uuid(String uuidValue) {
59+
this.value = Uuid.formatValue(uuidValue);
60+
}
61+
62+
public String getValue() {
63+
return this.value;
64+
}
65+
66+
private String generateValue() {
67+
String hexValue = EncodingUtil.convertToHex(Crypto.generateAesKey(128));
68+
69+
// Version Calculation: (i & 0x0f) | 0x40
70+
// Version Format: Always begins with 4
71+
String versionShiftedHexBits = this.getShiftedHexBits(hexValue.substring(14, 16), this.convertHexToInteger('0x0f'), this.convertHexToInteger('0x40'));
72+
73+
// Variant Calculation: (i & 0x3f) | 0x80
74+
// Variant Format: Always begins with 8, 9, A or B
75+
String variantShiftedHexBits = this.getShiftedHexBits(hexValue.substring(18, 20), this.convertHexToInteger('0x3f'), this.convertHexToInteger('0x80'));
76+
77+
String uuidValue = hexValue.substring(0, 8) // time-low
78+
+ hexValue.substring(8, 12) // time-mid
79+
+ versionShiftedHexBits + hexValue.substring(14, 16) // time-high-and-version
80+
+ variantShiftedHexBits + hexValue.substring(18, 20) // clock-seq-and-reserved + clock-seq-low
81+
+ hexValue.substring(20); // node
82+
83+
return Uuid.formatValue(uuidValue);
84+
}
85+
86+
private String getShiftedHexBits(String hexSubstring, Integer lowerThreshold, Integer upperThreshold) {
87+
Integer shiftedIntegerBits = (this.convertHexToInteger(hexSubstring) & lowerThreshold) | upperThreshold;
88+
return this.convertIntegerToHex(shiftedIntegerBits);
89+
}
90+
91+
private Integer convertHexToInteger(String hexValue) {
92+
hexValue = hexValue.toLowerCase();
93+
94+
if(hexValue.startsWith(HEX_PREFIX)) hexValue = hexValue.substringAfter(HEX_PREFIX);
95+
96+
Integer integerValue = 0;
97+
for(String hexCharacter : hexValue.split('')) {
98+
Integer hexCharacterIndex = HEX_CHARACTERS.indexOf(hexCharacter);
99+
100+
integerValue = HEX_BASE * integerValue + hexCharacterIndex;
101+
}
102+
return integerValue;
103+
}
104+
105+
private String convertIntegerToHex(Integer integerValue) {
106+
String hexValue = '';
107+
while(integerValue > 0) {
108+
Integer hexCharacterIndex = Math.mod(integerValue, HEX_BASE);
109+
110+
hexValue = HEX_CHARACTER_LIST[hexCharacterIndex] + hexValue;
111+
integerValue = integerValue / HEX_BASE;
112+
}
113+
return hexValue;
114+
}
115+
116+
private class UuidException extends Exception {}
117+
118+
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
3-
<apiVersion>38.0</apiVersion>
3+
<apiVersion>43.0</apiVersion>
44
<status>Active</status>
55
</ApexClass>

0 commit comments

Comments
 (0)