001 /* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at 010 * docs/licenses/cddl.txt 011 * or http://www.opensource.org/licenses/cddl1.php. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * docs/licenses/cddl.txt. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2010-2014 UnboundID Corp. 026 */ 027 package com.unboundid.directory.sdk.sync.util; 028 029 import com.unboundid.ldap.sdk.Attribute; 030 import com.unboundid.ldap.sdk.DN; 031 import com.unboundid.ldap.sdk.Entry; 032 import com.unboundid.ldap.sdk.RDN; 033 import com.unboundid.util.ThreadSafety; 034 import com.unboundid.util.ThreadSafetyLevel; 035 036 import java.util.ArrayList; 037 import java.util.Date; 038 import java.util.HashMap; 039 import java.util.List; 040 import java.util.Map; 041 import java.sql.Blob; 042 import java.sql.SQLException; 043 import java.sql.Timestamp; 044 import java.text.ParseException; 045 import java.text.SimpleDateFormat; 046 047 /** 048 * This class contains various utility methods for working with the 049 * UnboundID LDAP SDK and JDBC objects within script implementations. These 050 * are useful for populating LDAP entries with content from JDBC result sets 051 * and also for working with DNs, Timestamps, and other objects. 052 */ 053 @ThreadSafety(level = ThreadSafetyLevel.COMPLETELY_THREADSAFE) 054 public final class ScriptUtils 055 { 056 057 /** 058 * The date format string that will be used to construct and parse dates 059 * represented using generalized time with a four-digit year. 060 */ 061 private static final String DATE_FORMAT_GMT_TIME = 062 "yyyyMMddHHmmss'Z'"; 063 064 /** 065 * The date format string that will be used to construct and parse dates 066 * represented using generalized time. 067 */ 068 private static final String DATE_FORMAT_GENERALIZED_TIME = 069 "yyyyMMddHHmmss.SSS'Z'"; 070 071 // DateFormats are not thread-safe, so we provide a thread local 072 private static final ThreadLocal<SimpleDateFormat> gmtTime = 073 new ThreadLocal<SimpleDateFormat>() 074 { 075 @Override 076 protected SimpleDateFormat initialValue() 077 { 078 return new SimpleDateFormat(DATE_FORMAT_GMT_TIME); 079 } 080 }; 081 082 private static final ThreadLocal<SimpleDateFormat> generalizedTime = 083 new ThreadLocal<SimpleDateFormat>() 084 { 085 @Override 086 protected SimpleDateFormat initialValue() 087 { 088 return new SimpleDateFormat(DATE_FORMAT_GENERALIZED_TIME); 089 } 090 }; 091 092 /** 093 * Private constructor to enforce non-instantiability. 094 */ 095 private ScriptUtils() {} 096 097 /** 098 * Adds an {@link Attribute} with the given attribute name and string value to 099 * the given entry if the value is not null. 100 * @param entry 101 * an LDAP entry instance. May be null. 102 * @param attrName 103 * the name of the attribute to add to the entry. May <b>not</b> be 104 * null. 105 * @param value 106 * the value for the attribute to add to the entry. May be null. 107 */ 108 public static void addStringAttribute(final Entry entry, 109 final String attrName, 110 final String value) 111 { 112 if(entry != null && value != null) 113 { 114 entry.addAttribute(attrName, value); 115 } 116 } 117 118 /** 119 * Adds an {@link Attribute} with the given attribute name and numeric value 120 * to the given entry if the value is not null. 121 * @param entry 122 * an LDAP entry instance. May be null. 123 * @param attrName 124 * the name of the attribute to add to the entry. May <b>not</b> be 125 * null. 126 * @param value 127 * the value for the attribute to add to the entry. May be null. 128 */ 129 public static void addNumericAttribute(final Entry entry, 130 final String attrName, 131 final Number value) 132 { 133 if(entry != null && value != null) 134 { 135 entry.addAttribute(attrName, value.toString()); 136 } 137 } 138 139 /** 140 * Adds an {@link Attribute} with the given attribute name and boolean value 141 * to the given entry if the value is not null. If the boolean is 142 * <code>true</code>, the attribute value will be the string "true", otherwise 143 * it will be the string "false". 144 * @param entry 145 * an LDAP entry instance. May be null. 146 * @param attrName 147 * the name of the attribute to add to the entry. May <b>not</b> be 148 * null. 149 * @param value 150 * the value for the attribute to add to the entry. May be null. 151 */ 152 public static void addBooleanAttribute(final Entry entry, 153 final String attrName, 154 final Boolean value) 155 { 156 if(entry != null && value != null) 157 { 158 entry.addAttribute(attrName, value.toString()); 159 } 160 } 161 162 /** 163 * Adds an {@link Attribute} with the given attribute name and {@link Date} 164 * value to the given entry if the value is not null. The date is formatted 165 * using the generalized time syntax with a four-digit year and an optional 166 * milliseconds component (i.e. yyyyMMddHHmmss[.SSS]'Z'). 167 * @param entry 168 * entry an LDAP entry instance. May be null. 169 * @param attrName 170 * the name of the attribute to add to the entry. May <b>not</b> be 171 * null. 172 * @param date 173 * the Date value for the attribute to add to the entry. May be null. 174 * @param includeMilliseconds 175 * whether to include the milliseconds component in the attribute 176 * value 177 */ 178 public static void addDateAttribute(final Entry entry, 179 final String attrName, 180 final Date date, 181 final boolean includeMilliseconds) 182 { 183 if(entry != null && date != null) 184 { 185 String result; 186 if(includeMilliseconds) 187 { 188 result = generalizedTime.get().format(date); 189 } 190 else 191 { 192 result = gmtTime.get().format(date); 193 } 194 entry.addAttribute(attrName, result); 195 } 196 } 197 198 /** 199 * Adds an {@link Attribute} with the given attribute name and {@link Blob} 200 * value to the given entry if the underlying byte array is not null or empty. 201 * This method calls <code>free()</code> on the Blob object after the 202 * attribute has been added to the entry. 203 * @param entry 204 * an LDAP entry instance. May be null. 205 * @param attrName 206 * the name of the attribute to add to the entry. May <b>not</b> be 207 * null. 208 * @param value 209 * the value for the attribute to add to the entry. May be null. 210 * @param maxBytes 211 * the maximum number of bytes to extract from the Blob 212 * and add to the entry. 213 */ 214 public static void addBinaryAttribute(final Entry entry, 215 final String attrName, 216 final Blob value, 217 final int maxBytes) 218 { 219 if(entry != null && value != null) 220 { 221 try 222 { 223 byte[] bytes = value.getBytes(1, maxBytes); 224 if(bytes.length > 0) 225 { 226 entry.addAttribute(attrName, bytes); 227 } 228 value.free(); 229 } 230 catch(SQLException e) 231 { 232 // suppress 233 } 234 } 235 } 236 237 /** 238 * Returns true if the given attribute is not null and contains any one or 239 * more of the given string values; returns false otherwise. This method 240 * is case-insensitive. 241 * @param attr 242 * the {@link Attribute} whose values to check 243 * @param values 244 * the value(s) you are looking for 245 * @return true if any of the values were found in the attribute, false if not 246 */ 247 public static boolean containsAnyValue(final Attribute attr, 248 final String... values) 249 { 250 if(attr == null) 251 { 252 return false; 253 } 254 for(String attrValue : attr.getValues()) 255 { 256 for(String valueToCheck : values) 257 { 258 if(attrValue.equalsIgnoreCase(valueToCheck)) 259 { 260 return true; 261 } 262 } 263 } 264 return false; 265 } 266 267 /** 268 * String helper method to check if a value is a null object 269 * or empty string. 270 * @param value 271 * the String object to check 272 * @return true if the value is null or empty string, false otherwise 273 */ 274 public static boolean isNullOrEmpty(final String value) 275 { 276 if(value == null) 277 { 278 return true; 279 } 280 return value.isEmpty(); 281 } 282 283 /** 284 * Returns a SQL {@link Timestamp} based on the string value that is passed 285 * in. The string is parsed using generalized time syntax first with and then 286 * without milliseconds (i.e. yyyyMMddHHmmss[.SSS]'Z'). If the string cannot 287 * be parsed, <code>null</code> is returned. 288 * @param value 289 * a string that represents a timestamp in generalized time format 290 * @return a SQL Timestamp value set to the time that was passed in, or null 291 * if the value cannot be parsed 292 */ 293 public static Timestamp getTimestampFromString(final String value) 294 { 295 Timestamp ts = null; 296 if(value == null) 297 { 298 return ts; 299 } 300 301 // generalized time (with milliseconds) is a more specific format, 302 // so try to parse as that first 303 try 304 { 305 Date d = generalizedTime.get().parse(value); 306 ts = new Timestamp(d.getTime()); 307 return ts; 308 } 309 catch(ParseException e) 310 { 311 } 312 313 // try to parse as GMT time 314 try 315 { 316 Date d = gmtTime.get().parse(value); 317 ts = new Timestamp(d.getTime()); 318 } 319 catch(ParseException e) 320 { 321 } 322 323 return ts; 324 } 325 326 /** 327 * Takes an identifier string (for example from the database changelog table) 328 * and creates a DN from the components. If there are multiple primary keys 329 * in the identifier, they must be delimited by a unique string with which 330 * the identifier can be split. For example, you could specify a delimiter of 331 * "%%" to handle the following identifier: account_id=123%%group_id=5. 332 * <p> 333 * The resulting DN will contain a RDN per component, and the relative order 334 * of RDNs will be consistent with the order of the components in the original 335 * identifier string. The components here are usually 336 * primary keys for the entry in the database. 337 * @param identifiableInfo 338 * the identifier string for a given database entry. This cannot be 339 * null. 340 * @param delimiter 341 * The delimiter used to split separate components of the 342 * identifiable info. If this is null, the default of "%%" will be 343 * used. 344 * @return a DN representing the given identifier. 345 */ 346 public static DN idStringToDN(final String identifiableInfo, 347 final String delimiter) 348 { 349 String defaultDelimiter = delimiter; 350 if(delimiter == null || delimiter.isEmpty()) 351 { 352 defaultDelimiter = "%%"; //default delimiter 353 } 354 355 List<RDN> rdnList = new ArrayList<RDN>(); 356 String[] pairs = identifiableInfo.split(defaultDelimiter); 357 for(String pair : pairs) 358 { 359 String[] kv = pair.split("=", 2); 360 if(kv.length != 2) 361 { 362 throw new IllegalArgumentException("Malformed identifier component: " + 363 pair); 364 } 365 rdnList.add(new RDN(kv[0].trim(), kv[1].trim())); 366 } 367 368 if(rdnList.isEmpty()) 369 { 370 throw new IllegalArgumentException( 371 "The identifiableInfo parameter is empty."); 372 } 373 374 return new DN(rdnList); 375 } 376 377 /** 378 * Takes an identifier DN (such as the output from 379 * {@link #idStringToDN(String,String)} and 380 * creates a hash map of the components (RDN attributes) to their respective 381 * values. Each RDN will have a separate entry in the resulting map. 382 * <p> 383 * This method is meant to handle database entry identifier DNs, and as such 384 * does not handle DNs with multi-valued RDNs (i.e. 385 * pk1=John+pk2=Doe,groupID=123). 386 * @param identifiableInfo 387 * the identifier DN for a particular database entry. This cannot be 388 * null. 389 * @return a map of each RDN name to its value (from the given DN) 390 */ 391 public static Map<String, String> dnToMap(final DN identifiableInfo) 392 { 393 394 Map<String, String> ids = new HashMap<String, String>(); 395 for(RDN rdn : identifiableInfo.getRDNs()) 396 { 397 String[] kv = rdn.toString().split("=", 2); 398 ids.put(kv[0], kv[1]); 399 } 400 return ids; 401 } 402 }