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-2013 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 }