14
14
*/
15
15
package software .amazon .cloudformation .resource ;
16
16
17
+ import com .google .common .base .Splitter ;
18
+ import com .google .common .collect .Iterables ;
19
+ import java .util .Arrays ;
17
20
import java .util .Random ;
21
+ import java .util .regex .Pattern ;
18
22
import org .apache .commons .lang3 .RandomStringUtils ;
19
23
20
24
public class IdentifierUtils {
21
25
22
26
private static final int GENERATED_PHYSICALID_MAXLEN = 40 ;
23
27
private static final int GUID_LENGTH = 12 ;
28
+ private static final int MIN_PHYSICAL_RESOURCE_ID_LENGTH = 15 ;
29
+ private static final int MIN_PREFERRED_LENGTH = 17 ;
30
+ private static final Splitter STACKID_SPLITTER = Splitter .on ('/' );
31
+ private static final Pattern STACK_ARN_PATTERN = Pattern .compile ("^[a-z0-9-:]*stack/[-a-z0-9A-Z/]*" );
32
+ private static final Pattern STACK_NAME_PATTERN = Pattern .compile ("^[-a-z0-9A-Z]*" );
24
33
25
34
private IdentifierUtils () {
26
35
}
@@ -56,7 +65,7 @@ public static String generateResourceIdentifier(final String logicalResourceId,
56
65
generateResourceIdentifier (final String logicalResourceId , final String clientRequestToken , final int maxLength ) {
57
66
int maxLogicalIdLength = maxLength - (GUID_LENGTH + 1 );
58
67
59
- int endIndex = logicalResourceId .length () > maxLogicalIdLength ? maxLogicalIdLength : logicalResourceId . length ( );
68
+ int endIndex = Math . min ( logicalResourceId .length (), maxLogicalIdLength );
60
69
61
70
StringBuilder sb = new StringBuilder ();
62
71
if (endIndex > 0 ) {
@@ -66,4 +75,92 @@ public static String generateResourceIdentifier(final String logicalResourceId,
66
75
return sb .append (RandomStringUtils .random (GUID_LENGTH , 0 , 0 , true , true , null , new Random (clientRequestToken .hashCode ())))
67
76
.toString ();
68
77
}
78
+
79
+ public static String generateResourceIdentifier (final String stackId ,
80
+ final String logicalResourceId ,
81
+ final String clientRequestToken ,
82
+ final int maxLength ) {
83
+
84
+ if (maxLength < MIN_PHYSICAL_RESOURCE_ID_LENGTH ) {
85
+ throw new IllegalArgumentException ("Cannot generate resource IDs shorter than " + MIN_PHYSICAL_RESOURCE_ID_LENGTH
86
+ + " characters." );
87
+ }
88
+
89
+ String stackName = stackId ;
90
+
91
+ if (isStackArn (stackId )) {
92
+ stackName = STACKID_SPLITTER .splitToList (stackId ).get (1 );
93
+ }
94
+
95
+ if (!isValidStackName (stackName )) {
96
+ throw new IllegalArgumentException (String .format ("%s is not a valid Stack name" , stackName ));
97
+ }
98
+
99
+ // some services don't allow leading dashes. Since stack name is first, clean
100
+ // off any + no consecutive dashes
101
+
102
+ final String cleanStackName = stackName .replaceFirst ("^-+" , "" ).replaceAll ("-{2,}" , "-" );
103
+
104
+ final boolean separate = maxLength > MIN_PREFERRED_LENGTH ;
105
+ // 13 char length is reserved for the hashed value and one
106
+ // for each dash separator (if needed). the rest if the characters
107
+ // will get allocated evenly between the stack and resource names
108
+
109
+ final int freeCharacters = maxLength - 13 - (separate ? 1 : 0 );
110
+ final int [] requestedLengths = new int [2 ];
111
+
112
+ requestedLengths [0 ] = cleanStackName .length ();
113
+ requestedLengths [1 ] = logicalResourceId .length ();
114
+
115
+ final int [] availableLengths = fairSplit (freeCharacters , requestedLengths );
116
+ final int charsForStackName = availableLengths [0 ];
117
+ final int charsForResrcName = availableLengths [1 ];
118
+
119
+ final StringBuilder prefix = new StringBuilder ();
120
+
121
+ prefix .append (cleanStackName , 0 , charsForStackName );
122
+ if (separate ) {
123
+ prefix .append ("-" );
124
+ }
125
+ prefix .append (logicalResourceId , 0 , charsForResrcName );
126
+
127
+ return IdentifierUtils .generateResourceIdentifier (prefix .toString (), clientRequestToken , maxLength );
128
+ }
129
+
130
+ private static boolean isStackArn (String stackId ) {
131
+ return STACK_ARN_PATTERN .matcher (stackId ).matches () && Iterables .size (STACKID_SPLITTER .split (stackId )) == 3 ;
132
+ }
133
+
134
+ private static boolean isValidStackName (String stackName ) {
135
+ return STACK_NAME_PATTERN .matcher (stackName ).matches ();
136
+ }
137
+
138
+ private static int [] fairSplit (final int cap , final int [] buckets ) {
139
+ int remaining = cap ;
140
+
141
+ int [] allocated = new int [buckets .length ];
142
+ Arrays .fill (allocated , 0 );
143
+
144
+ while (remaining > 0 ) {
145
+ // at least one capacity unit
146
+ int maxAllocation = remaining < buckets .length ? 1 : remaining / buckets .length ;
147
+
148
+ int bucketSatisfied = 0 ; // reset on each cap
149
+
150
+ for (int i = -1 ; ++i < buckets .length ;) {
151
+ if (allocated [i ] < buckets [i ]) {
152
+ final int incrementalAllocation = Math .min (maxAllocation , buckets [i ] - allocated [i ]);
153
+ allocated [i ] += incrementalAllocation ;
154
+ remaining -= incrementalAllocation ;
155
+ } else {
156
+ bucketSatisfied ++;
157
+ }
158
+
159
+ if (remaining <= 0 || bucketSatisfied == buckets .length ) {
160
+ return allocated ;
161
+ }
162
+ }
163
+ }
164
+ return allocated ;
165
+ }
69
166
}
0 commit comments