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 * http://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 module hunt.shiro.crypto.hash.format.DefaultHashFormatFactory; 20 21 import hunt.shiro.crypto.hash.format.ModularCryptFormat; 22 // import hunt.shiro.util.ClassUtils; 23 // import hunt.shiro.util.StringUtils; 24 // import hunt.shiro.util.UnknownClassException; 25 import hunt.shiro.crypto.hash.format.HashFormat; 26 import hunt.shiro.crypto.hash.format.HashFormatFactory; 27 28 import hunt.collection.HashMap; 29 import hunt.collection.HashSet; 30 import hunt.collection.Map; 31 import hunt.collection.Set; 32 import hunt.Exceptions; 33 34 import std.string; 35 36 /** 37 * This default {@code HashFormatFactory} implementation heuristically determines a {@code HashFormat} class to 38 * instantiate based on the input argument and returns a new instance of the discovered class. The heuristics are 39 * detailed in the {@link #getInstance(string) getInstance} method documentation. 40 * 41 */ 42 class DefaultHashFormatFactory : HashFormatFactory { 43 44 private Map!(string, string) formatClassNames; //id - to - fully qualified class name 45 46 private Set!(string) searchPackages; //packages to search for HashFormat implementations 47 48 this() { 49 this.searchPackages = new HashSet!(string)(); 50 this.formatClassNames = new HashMap!(string, string)(); 51 } 52 53 /** 54 * Returns a {@code hashFormatAlias}-to-<code>fullyQualifiedHashFormatClassNameImplementation</code> map. 55 * <p/> 56 * This map will be used by the {@link #getInstance(string) getInstance} implementation: that method's argument 57 * will be used as a lookup key to this map. If the map returns a value, that value will be used to instantiate 58 * and return a new {@code HashFormat} instance. 59 * <h3>Defaults</h3> 60 * Shiro's default HashFormat implementations (as listed by the {@link ProvidedHashFormat} enum) will 61 * be searched automatically independently of this map. You only need to populate this map with custom 62 * {@code HashFormat} implementations that are <em>not</em> already represented by a {@code ProvidedHashFormat}. 63 * <h3>Efficiency</h3> 64 * Populating this map will be more efficient than configuring {@link #getSearchPackages() searchPackages}, 65 * but search packages may be more convenient depending on the number of {@code HashFormat} implementations that 66 * need to be supported by this factory. 67 * 68 * @return a {@code hashFormatAlias}-to-<code>fullyQualifiedHashFormatClassNameImplementation</code> map. 69 */ 70 Map!(string, string) getFormatClassNames() { 71 return formatClassNames; 72 } 73 74 /** 75 * Sets the {@code hash-format-alias}-to-{@code fullyQualifiedHashFormatClassNameImplementation} map to be used in 76 * the {@link #getInstance(string)} implementation. See the {@link #getFormatClassNames()} JavaDoc for more 77 * information. 78 * <h3>Efficiency</h3> 79 * Populating this map will be more efficient than configuring {@link #getSearchPackages() searchPackages}, 80 * but search packages may be more convenient depending on the number of {@code HashFormat} implementations that 81 * need to be supported by this factory. 82 * 83 * @param formatClassNames the {@code hash-format-alias}-to-{@code fullyQualifiedHashFormatClassNameImplementation} 84 * map to be used in the {@link #getInstance(string)} implementation. 85 */ 86 void setFormatClassNames(Map!(string, string) formatClassNames) { 87 this.formatClassNames = formatClassNames; 88 } 89 90 /** 91 * Returns a set of package names that can be searched for {@link HashFormat} implementations according to 92 * heuristics defined in the {@link #getHashFormatClass(string, string) getHashFormat(packageName, token)} JavaDoc. 93 * <h3>Efficiency</h3> 94 * Configuring this property is not as efficient as configuring a {@link #getFormatClassNames() formatClassNames} 95 * map, but it may be more convenient depending on the number of {@code HashFormat} implementations that 96 * need to be supported by this factory. 97 * 98 * @return a set of package names that can be searched for {@link HashFormat} implementations 99 * @see #getHashFormatClass(string, string) 100 */ 101 Set!(string) getSearchPackages() { 102 return searchPackages; 103 } 104 105 /** 106 * Sets a set of package names that can be searched for {@link HashFormat} implementations according to 107 * heuristics defined in the {@link #getHashFormatClass(string, string) getHashFormat(packageName, token)} JavaDoc. 108 * <h3>Efficiency</h3> 109 * Configuring this property is not as efficient as configuring a {@link #getFormatClassNames() formatClassNames} 110 * map, but it may be more convenient depending on the number of {@code HashFormat} implementations that 111 * need to be supported by this factory. 112 * 113 * @param searchPackages a set of package names that can be searched for {@link HashFormat} implementations 114 */ 115 void setSearchPackages(Set!(string) searchPackages) { 116 this.searchPackages = searchPackages; 117 } 118 119 HashFormat getInstance(string in1) { 120 if (in1 is null) { 121 return null; 122 } 123 124 HashFormat hashFormat = null; 125 TypeInfo_Class clazz = null; 126 enum delimiter = ModularCryptFormat.TOKEN_DELIMITER; 127 128 //NOTE: this code block occurs BEFORE calling getHashFormatClass(in) on purpose as a performance 129 //optimization. If the input arg is an MCF-formatted string, there will be many unnecessary ClassLoader 130 //misses which can be slow. By checking the MCF-formatted option, we can significantly improve performance 131 if (in1.startsWith(delimiter)) { 132 //odds are high that the input argument is not a fully qualified class name or a format key (e.g. 'hex', 133 //base64' or 'shiro1'). Try to find the key and lookup via that: 134 string test = in1[delimiter.length .. $]; 135 string[] tokens = test.split("\\" ~ delimiter); 136 //the MCF ID is always the first token in the delimited string: 137 string possibleMcfId = (tokens !is null && tokens.length > 0) ? tokens[0] : null; 138 if (possibleMcfId !is null) { 139 //found a possible MCF ID - test it using our heuristics to see if we can find a corresponding class: 140 clazz = getHashFormatClass(possibleMcfId); 141 } 142 } 143 144 if (clazz is null) { 145 //not an MCF-formatted string - use the unaltered input arg and go through our heuristics: 146 clazz = getHashFormatClass(in1); 147 } 148 149 if (clazz !is null) { 150 //we found a HashFormat class - instantiate it: 151 hashFormat = newHashFormatInstance(clazz); 152 } 153 154 return hashFormat; 155 } 156 157 /** 158 * Heuristically determine the fully qualified HashFormat implementation class name based on the specified 159 * token. 160 * <p/> 161 * This implementation functions as follows (in order): 162 * <ol> 163 * <li>See if the argument can be used as a lookup key in the {@link #getFormatClassNames() formatClassNames} 164 * map. If a value (a fully qualified class name {@link HashFormat HashFormat} implementation) is found, 165 * {@link ClassUtils#forName(string) lookup} the class and return it.</li> 166 * <li> 167 * Check to see if the token argument is a 168 * {@link ProvidedHashFormat} enum value. If so, acquire the corresponding {@code HashFormat} class and 169 * return it. 170 * </li> 171 * <li> 172 * Check to see if the token argument is itself a fully qualified class name. If so, try to load the class 173 * and return it. 174 * </li> 175 * <li>If the above options do not result in a discovered class, search all all configured 176 * {@link #getSearchPackages() searchPackages} using heuristics defined in the 177 * {@link #getHashFormatClass(string, string) getHashFormatClass(packageName, token)} method documentation 178 * (relaying the {@code token} argument to that method for each configured package). 179 * </li> 180 * </ol> 181 * <p/> 182 * If a class is not discovered via any of the above means, {@code null} is returned to indicate the class 183 * could not be found. 184 * 185 * @param token the string token from which a class name will be heuristically determined. 186 * @return the discovered HashFormat class implementation or {@code null} if no class could be heuristically determined. 187 */ 188 protected TypeInfo_Class getHashFormatClass(string token) { 189 190 implementationMissing(false); 191 return null; 192 // Class clazz = null; 193 194 // //check to see if the token is a configured FQCN alias. This is faster than searching packages, 195 // //so we try this first: 196 // if (this.formatClassNames !is null) { 197 // string value = this.formatClassNames.get(token); 198 // if (value !is null) { 199 // //found an alias - see if the value is a class: 200 // clazz = lookupHashFormatClass(value); 201 // } 202 // } 203 204 // //check to see if the token is one of Shiro's provided FQCN aliases (again, faster than searching): 205 // if (clazz is null) { 206 // ProvidedHashFormat provided = ProvidedHashFormat.byId(token); 207 // if (provided !is null) { 208 // clazz = provided.getHashFormatClass(); 209 // } 210 // } 211 212 // if (clazz is null) { 213 // //check to see if 'token' was a FQCN itself: 214 // clazz = lookupHashFormatClass(token); 215 // } 216 217 // if (clazz is null) { 218 // //token wasn't a FQCN or a FQCN alias - try searching in configured packages: 219 // if (this.searchPackages !is null) { 220 // foreach(string packageName ; this.searchPackages) { 221 // clazz = getHashFormatClass(packageName, token); 222 // if (clazz !is null) { 223 // //found it: 224 // break; 225 // } 226 // } 227 // } 228 // } 229 230 // if (clazz !is null) { 231 // assertHashFormatImpl(clazz); 232 // } 233 234 // return clazz; 235 } 236 237 /** 238 * Heuristically determine the fully qualified {@code HashFormat} implementation class name in the specified 239 * package based on the provided token. 240 * <p/> 241 * The token is expected to be a relevant fragment of an unqualified class name in the specified package. 242 * A 'relevant fragment' can be one of the following: 243 * <ul> 244 * <li>The {@code HashFormat} implementation unqualified class name</li> 245 * <li>The prefix of an unqualified class name ending with the text {@code Format}. The first character of 246 * this prefix can be upper or lower case and both options will be tried.</li> 247 * <li>The prefix of an unqualified class name ending with the text {@code HashFormat}. The first character of 248 * this prefix can be upper or lower case and both options will be tried.</li> 249 * <li>The prefix of an unqualified class name ending with the text {@code CryptoFormat}. The first character 250 * of this prefix can be upper or lower case and both options will be tried.</li> 251 * </ul> 252 * <p/> 253 * Some examples: 254 * <table> 255 * <tr> 256 * <th>Package Name</th> 257 * <th>Token</th> 258 * <th>Expected Output Class</th> 259 * <th>Notes</th> 260 * </tr> 261 * <tr> 262 * <td>{@code com.foo.whatever}</td> 263 * <td>{@code MyBarFormat}</td> 264 * <td>{@code com.foo.whatever.MyBarFormat}</td> 265 * <td>Token is a complete unqualified class name</td> 266 * </tr> 267 * <tr> 268 * <td>{@code com.foo.whatever}</td> 269 * <td>{@code Bar}</td> 270 * <td>{@code com.foo.whatever.BarFormat} <em>or</em> {@code com.foo.whatever.BarHashFormat} <em>or</em> 271 * {@code com.foo.whatever.BarCryptFormat}</td> 272 * <td>The token is only part of the unqualified class name - i.e. all characters in front of the {@code *Format} 273 * {@code *HashFormat} or {@code *CryptFormat} suffix. Note that the {@code *Format} variant will be tried before 274 * {@code *HashFormat} and then finally {@code *CryptFormat}</td> 275 * </tr> 276 * <tr> 277 * <td>{@code com.foo.whatever}</td> 278 * <td>{@code bar}</td> 279 * <td>{@code com.foo.whatever.BarFormat} <em>or</em> {@code com.foo.whatever.BarHashFormat} <em>or</em> 280 * {@code com.foo.whatever.BarCryptFormat}</td> 281 * <td>Exact same output as the above {@code Bar} input example. (The token differs only by the first character)</td> 282 * </tr> 283 * </table> 284 * 285 * @param packageName the package to search for matching {@code HashFormat} implementations. 286 * @param token the string token from which a class name will be heuristically determined. 287 * @return the discovered HashFormat class implementation or {@code null} if no class could be heuristically determined. 288 */ 289 // protected Class getHashFormatClass(string packageName, string token) { 290 // string test = token; 291 // Class clazz = null; 292 // string pkg = packageName is null ? "" : packageName; 293 294 // //1. Assume the arg is a fully qualified class name in the classpath: 295 // clazz = lookupHashFormatClass(test); 296 297 // if (clazz is null) { 298 // test = pkg ~ "." ~ token; 299 // clazz = lookupHashFormatClass(test); 300 // } 301 302 // if (clazz is null) { 303 // test = pkg ~ "." ~ StringUtils.uppercaseFirstChar(token) ~ "Format"; 304 // clazz = lookupHashFormatClass(test); 305 // } 306 307 // if (clazz is null) { 308 // test = pkg ~ "." ~ token ~ "Format"; 309 // clazz = lookupHashFormatClass(test); 310 // } 311 312 // if (clazz is null) { 313 // test = pkg ~ "." ~ StringUtils.uppercaseFirstChar(token) ~ "HashFormat"; 314 // clazz = lookupHashFormatClass(test); 315 // } 316 317 // if (clazz is null) { 318 // test = pkg ~ "." ~ token ~ "HashFormat"; 319 // clazz = lookupHashFormatClass(test); 320 // } 321 322 // if (clazz is null) { 323 // test = pkg ~ "." ~ StringUtils.uppercaseFirstChar(token) ~ "CryptFormat"; 324 // clazz = lookupHashFormatClass(test); 325 // } 326 327 // if (clazz is null) { 328 // test = pkg ~ "." ~ token ~ "CryptFormat"; 329 // clazz = lookupHashFormatClass(test); 330 // } 331 332 // if (clazz is null) { 333 // return null; //ran out of options 334 // } 335 336 // assertHashFormatImpl(clazz); 337 338 // return clazz; 339 // } 340 341 // protected Class lookupHashFormatClass(string name) { 342 // try { 343 // return ClassUtils.forName(name); 344 // } catch (UnknownClassException ignored) { 345 // } 346 347 // return null; 348 // } 349 350 protected final void assertHashFormatImpl(TypeInfo_Class clazz) { 351 // if (!HashFormat.class.isAssignableFrom(clazz) || clazz.isInterface()) { 352 // throw new IllegalArgumentException("Discovered class [" ~ clazz.getName() ~ "] is not a " ~ 353 // "HashFormat implementation."); 354 // } 355 } 356 357 protected final HashFormat newHashFormatInstance(TypeInfo_Class clazz) { 358 assertHashFormatImpl(clazz); 359 return cast(HashFormat) clazz.create(); 360 } 361 }