Utils.java
/*
** Module : Utils.java
** Abstract : General purpose utility methods
**
** Copyright (c) 2004-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------------Description-----------------------------------
** 001 ECF 20041122 @18804 Created initial version, consisting of some
** Properties object helper methods.
** 002 NVS 20050223 @19935 Added readPassphrase method.
** 003 ECF 20050225 @19962 Added printChainedErrors method.
** 004 ECF 20050225 @20061 Added findFileInPath method. Generalized and
** moved from findFile in uast.AstGenerator.
** 005 GES 20050304 @20196 Added file copying methods.
** 006 GES 20050311 @20293 Added isLaterVersion method.
** 007 ECF 20050329 @20519 Modified printChainedErrors method. Now
** prints name of throwable type if no message
** is present.
** 008 ECF 20050422 @20844 Added indent parameter to printChainedErrors
** method. Indents each message line by the
** specified number of spaces.
** 009 GES 20050706 @21667 Added append mode to copyFile().
** 010 ECF 20051103 @23222 Added getLog method. Retrieves an Apache
** commons-logging logging object scoped to a
** particular package.
** 011 ECF 20051104 @23239 Added describeContext method. Uniquely
** describes current context for debugging or
** logging.
** 012 ECF 20060112 @23897 Added verifyDirectoryNode method. Verifies
** existence and class of a required directory
** entry.
** 013 GES 20060122 @24013 Added directory query helpers to manage
** the entire process of searching for a
** specified value across a pre-defined
** hierarchy in the directory.
** 014 ECF 20060123 @24039 Modified describeContext method. Combine
** session ID, thread ID, and user ID to create
** a unique string.
** 015 GES 20060125 @24073 Added directory node search helper.
** 016 NVS 20060226 @24749 Added generic method search and invocation
** service invoke().
** 017 NVS 20060227 @24771 Added noHelp() method which is a placeholder
** for a real help application.
** 018 ECF 20060309 @24998 Added getUnqualifiedName() method. Gets the
** short name of a class or interface.
** 019 GES 20060609 @27053 Removed fillInStackTrace() from invoke()
** since this only destroys the exception's
** stack trace (it doesn't affect any chained
** cause's stack trace). It is better to have
** the original record of where the problem
** occurred than to know that we rethrew the
** exception from this point.
** 020 GES 20060802 @28296 Created primitive/wrapper conversion helpers
** to simplify list and array processing.
** 021 ECF 20060918 @29668 Temporary fix to describeContext(). Handle
** invalid thread ID returned from
** SecurityManager. A better fix would be to
** ensure SecurityManager never returns a null
** thread ID.
** 022 GES 20061220 @31807 Removed readPassphrase() and replaced it
** with prompt() that is a simpler and safer
** alternative.
** 023 NVS 20070112 @31893 Added uniqueId() method and supporting data
** in form of a context local work area.
** Added methods to work with sets: intersects()
** intersection() and difference().
** 024 NVS 20070126 @32010 Added scope param to findDirectoryNodePath()
** method. Now the method can do server level
** searches (/server/<serverId> and
** /server/default paths) and account level
** searches as the original implementation.
** 025 NVS 20070404 @32764 Added new method diffIntArrays().
** 026 GES 20070716 @34530 Moved file copying related methods here for
** better reuse.
** 027 ECF 20071009 @35600 Added getPortForService() and variants of
** other directory convenience methods. The
** former looks up the port number for a given
** service name. The latter add a 'scope'
** parameter to permit looking up values of each
** basic data type by account/group or by
** server.
** 028 ECF 20071101 @35897 Changed invoke() signature. Now uses var-args
** instead of an Object array for parameters, if
** any, to be passed to the invoked method.
** 029 GES 20080414 @38023 Added helpers for text processing which
** honors the proper charset conversions.
** 030 GES 20080421 @38058 Added another charset helper.
** 031 CA 20081006 @40061 Added integerCollectionToPrimitive.
** 032 CA 20081205 @40820 Added integerMapToPrimitive.
** 033 CA 20090413 @41770 Fixed bug in integerMapToPrimitive - index was
** not incremented.
** 034 GES 20090422 @41918 Converted to standard string formatting.
** 035 GES 20090424 @41975 Import change.
** 036 NVS 20090624 @42833 Added new method removeDirectoryNode().
** 037 SIY 20090701 @43013 Added scroll() method.
** Removed integerListToPrimitive() which is a copy
** of integerCollectionToPrimitive(). Inferred
** generics in order to improve type checking.
** 038 SIY 20090923 @43987 Directory binding in getDirectoryNodeWorker now is
** optional if directory is already bound.
** 039 ECF 20101004 Added methods to match a file path case-
** insensitively and to canonicalize a file system
** path.
** 040 SIY 20110112 Changes related to new color handling scheme. Minor
** cleanups.
** 041 GES 20110623 Fixed case sensitive filename matching helpers.
** 042 CA 20130822 Added API to check if two sets match.
** 043 HC 20131031 Support methods for Long resource IDs, see issue #2183.
** 044 CA 20140206 Improved the directory node lookup APIs, to allow search using an
** explicit account list and to allow fallback to a per-server value, if
** per-account search failed.
** 045 OM 20140706 Fixed matchPathCaseInsensitively for Windows filesystem.
** 046 GES 20141106 Improved some javadocs and added unimplemented feature logging.
** 047 CA 20150302 Add logging if a directory node is found with an unexpected class.
** 048 ECF 20150715 Replace StringBuffer with StringBuilder. Deprecated getLog.
** 049 GES 20150923 Added causeChainContains().
** 050 OM 20151130 Eliminated IOException from canonicalizePath. Declared getLog()
** method as @Deprecated because we are moving towards J2SE logging.
** 051 HC 20151213 Improved DIALOG-BOX window parenting logic and related changes.
** 052 CA 20151229 If instantiation of entry point fails with an ERROR condition, then
** show the error message.
** 053 EVL 20160224 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 054 IAS 20160229 Added file extension extractor, tryWhileNull utility method and
** InstrumentedMap.
** 055 ECF 20160202 Removed deprecated getLog method.
** 056 IAS 20160331 Fixed intersect() and isSame() to avoid NPE.
** 057 OM 20160422 Added per-project support.
** 058 EVL 20170831 Adding helper method to convert string to file size integer value.
** 059 ECF 20171121 Modified intersect method to exit quickly if sets are empty.
** GES 20171122 Added split() to more efficiently calculate both the differences
** and intersection, when both are needed anyway.
** 060 SBI 20180410 Added calculateResourcePath to be used by gui drivers.
** 061 SBI 20180521 Added getPathResolver and getSafePathResolver.
** 062 SBI 20180531 Added common Utils.getDirectoryNodeMap.
** 063 SBI 20180716 Generalized code normalizeZorder and Zkey, moveToTopInClass and
** moveToBottomInClass from AbstractContainer.
** 064 HC 20180813 Added double data type to the directory access interface.
** 065 HC 20190701 Added methods longToBytesLE and bytesToLongLE.
** 066 CA 20190731 Added findFieldWithValue.
** CA 20190811 Added collectInterfaces.
** 067 ECF 20190619 Refactored replaceSeparator from canonicalizePath.
** 068 CA 20191119 Track the instantiating class, to be able to register any buffer to
** its source definition class.
** CA 20191212 Added findConstructor, to find the first ctor which matches the
** argument's class or super-class, or any of its super-interfaces.
** 069 HC 20200130 Added a method for enumerating nodes.
** 070 SBI 20200225 Moved isValidValue to this helper class.
** 071 CA 20200122 Javadoc fixes.
** 072 HC 20200726 Added enumDirectoryNodes method.
** GES 20200922 Minor optimizations in set processing.
** CA 20200924 Added Utils.invoke, to execute methods via ReflectASM instead of Method.invoke.
** CA 20201003 Use an identity HashSet where possible.
** CA 20201011 Added intersect() and containsAll() for RoaringBitmap (fast-access bitmap used
** instead of integer sets).
** ME 20210121 Fix longToBytesLE to use little end order.
** RFB 20210331 Added Utils.onlyDigits to assist FillIn widget updates. Ref #5210.
** RFB 20210402 No need for onlyDigits, as NumberType.parseDoubleTest was added to handle this
** need. Minor javadoc fix, as well. Ref #5210.
** SBI 20210413 Added generic getDirectoryNodeBeanList to get simple java bean from the directory settings.
** 20210414 Added generic searchResourceJars, moved getRefinedPathName from FileSystemOps.
** CA 20210407 Throw NPE if if invoking an instance method and the instance is null.
** SBI 20210621 Changed getDirectoryNodeBeanList to get properties from node's attributes.
** 20210701 Added the common code to getOrCreateDefaultFwdTemporaryDirectory(), SupplierWithMemo.
** HC 20210727 Fixed doWithBoundDS to bind the supplied directory service if needed.
** RFB 20210805 Implemented safe isServer/isClient/isChUI/isGUI for use on client of server.
** Ref #4984.
** SBI 20210813 Changed searchResourceJars to make it independent from the server side,
** added getJarPath
** ECF 20210916 describeContext now returns only the thread name if not running on the server.
** CA 20210928 Added getOrCreateTemporaryDirectory.
** CA 20211004 Automatically delete the created temporary folder (and its content), on JVM
** shutdown.
** CA 20211019 The temporary folder name must be unique for each context (previously was a
** static variable).
** VVT 20211020 getOrCreateTemporaryDirectory() fixed: cleanup hook must be added only once per directory.
** (ref. #5744).
** VVT 20211021 Restored statements unnecessarily nested within else clause, removed
** earlier in the previous change.
** CA 20211025 Added dfsClassHierarchy.
** HC 20220307 Added disableClientSSLCertificateValidation to allow disabling of SSL
** certificate validation on HTTPS client connections.
** SBI 20220427 Added int convertCodepoint(int, String), getCodepoint(int, String) and
** changed char toChar(int) to use cpinternal charset.
** RFB 20220504 Minor javadoc fixes.
** SBI 20220504 Fixed toChar(int, String) to return -1 if key code is not represented by
** the given char set.
** TJD 20220504 Java 11 compatibility minor changes
** 073 SBI 20230224 Added getCharsetOverride(String charsetName).
** 074 SBI 20230411 Added isEndPointAvailable.
** 075 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 076 CA 20230601 Changed 'collectInterfaces' to perform a BFS walk of the type hierarchy. Also,
** this fixes a bug where only the direct interfaces were collected.
** 077 GBB 20230608 pollAll method added to empty a Queue into a List.
** 078 GBB 20230616 Remove stacktrace from log msg `not available host:port` to declutter log files.
** 079 GBB 20230620 createThreadName method added (originally defined in LogHelper).
** 080 TT 20230626 Added support for an explicitly set temp directory (-T).
** getOrCreateTemporaryDirectory() now accepts an additional parameter called explicitFolderName.
** Added the methods getTmpDir() and tryToSetTempDir().
** Added a Boolean version of getDirectoryNodeBoolean().
** 081 GBB 20230825 SecurityManager context & session methods calls updated.
** getCustomPackageRoot added.
** 082 TT 20230915 Error handling using PostInitErrorManager when trying to setTemporaryDirectory().
** 083 GBB 20240404 JarResource class and searchResourceJars() moved to JarUtil. logProcessError()
** moved here from ClientCore to be reused and improved to not block on read.
** getCustomPackageRoot() to use OS independent file separators.
** 084 GBB 20240515 Adding convenience overload for getDirectoryNodeStrings().
** 085 GBB 20240613 Process identifier added to logs of process error handling.
** 086 GBB 20240709 Removing redundant getDirectoryNodeStrings overload method.
** 087 GBB 20240718 Handle more gracefully setting project token.
** 088 CA 20240722 Added 'getDirectoryNodeMap' which allows a 'null' DirectoryService argument.
** 089 ICP 20240722 Modified shutdown hook registration.
** 090 VVT 20240826 SourceNameMapper.convertJavaProg() renamed. See #8613-16.
** 091 GBB 20240828 Moving OSResourceManager and FileSystem to osresource package.
** 092 SBI 20240902 Fixed moveToTop[Bottom]InClass according to its internal logic, removed dead code.
** 093 CA 20240910 Added 'isRunningJava8', used to enable 'net:socket:nio' by default.
** 094 SBI 20240930 Added toURI.
** 095 SBI 20250126 Removed usages of I18nOPS from toChar(.)
** 096 AS 20250529 Added pathCache to cache the properly formatted case sensitive image path string.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.util;
import java.io.*;
import java.security.*;
import java.security.cert.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.*;
import java.util.logging.*;
import java.util.stream.*;
import com.goldencode.cache.ExpiryCache;
import com.goldencode.p2j.main.OrderedShutdownHooks;
import com.goldencode.p2j.persist.CacheManager;
import com.goldencode.p2j.util.logging.*;
import com.goldencode.p2j.util.osresource.*;
import com.goldencode.p2j.util.osresource.FileSystem;
import com.goldencode.util.*;
import org.roaringbitmap.*;
import org.roaringbitmap.longlong.*;
import java.net.*;
import java.nio.*;
import java.nio.charset.*;
import java.nio.file.*;
import java.lang.invoke.*;
import java.lang.reflect.*;
import com.esotericsoftware.reflectasm.*;
import com.goldencode.p2j.*;
import com.goldencode.p2j.convert.*;
import com.goldencode.p2j.directory.*;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.security.SecurityManager;
import com.goldencode.p2j.ui.LogicalTerminal;
import com.goldencode.p2j.ui.ZOrderClass;
import com.goldencode.p2j.ui.chui.ThinClient;
import javax.net.ssl.*;
/**
* Collection of useful, static, utility methods for various tasks.
*/
public final class Utils
{
/** Define search modes for the directory node lookups. */
public static enum DirScope
{
SERVER,
ACCOUNTS,
BOTH
}
/** Global cache of ReflectASM information for {@link #invoke(Method, Object, Object...)}. */
private static final Map<Class<?>, MethodData> METHOD_DATA = new ConcurrentHashMap<>();
/** logger */
private static final CentralLogger LOG = CentralLogger.get(Utils.class.getName());
/** Stores context-local state variables. */
private static ContextContainer work = new ContextContainer();
/** Logging token that ensures unimplemented feature logging occurs only once. */
private static Object token = null;
/** A cache of single-arg constructors in all possible {@link BaseDataType} sub-classes. */
private static Map<String, Constructor<?>> bdtConstructors;
/** A cache to store the properly formatted case sensitive image path string. */
private static ExpiryCache<String, String> pathCache;
static
{
bdtConstructors = new HashMap<>();
try
{
for (ParmType ptype : ParmType.values())
{
Class<?> cls = ptype.getParamType();
if (BaseDataType.class.isAssignableFrom(cls) && cls != BaseDataType.class)
{
Constructor<?>[] ctors = cls.getConstructors();
for (Constructor<?> ctor : ctors)
{
Class<?>[] ptypes = ctor.getParameterTypes();
if (ptypes.length != 1)
{
continue;
}
Class<?> argType = ptypes[0];
String key = cls.toString() + "#" + argType.toString();
ctor.setAccessible(true);
bdtConstructors.put(key, ctor);
}
}
}
}
catch (Throwable t)
{
// within PL/Java, this will not be able to initialize
LOG.warning("Could not populate BDT constructors!");
}
bdtConstructors = Collections.unmodifiableMap(bdtConstructors);
}
/**
* Private constructor to prevent instances of this class from being
* created.
*/
private Utils()
{
}
/**
* Check if the current Java version is java 8.
*
* @return See above.
*/
public static boolean isRunningJava8()
{
String ver = System.getProperty("java.version");
return ver.startsWith("1.8");
}
/**
* Find the first single-argument constructor which has a single parameter with the argument's
* type or any of its super-classes or super-interfaces.
*
* @param cls
* The class where to search for the c'tor.
* @param arg
* The argument's type.
*
* @return The found c'tor.
*/
public static Constructor<?> findConstructor(Class<?> cls, Class<?> arg)
{
String key = cls.toString() + "#";
Class<?> parent = arg;
while (parent != null)
{
Constructor<?> ctor = bdtConstructors.get(key + parent.toString());
if (ctor != null)
{
return ctor;
}
parent = parent.getSuperclass();
}
// look for the interfaces
Set<Class<?>> ifaces = Collections.newSetFromMap(new IdentityHashMap<>());
collectInterfaces(cls, ifaces);
for (Class<?> ifc : ifaces)
{
Constructor<?> ctor = bdtConstructors.get(key + ifc.toString());
if (ctor != null)
{
return ctor;
}
}
return null;
}
/**
* Scroll given array so element with specified index will be first in
* array. All elements before it will be placed at the end of the array.
* <p>
* NOTE: Scrolling is performed "in place", so input array gets updated!
*
* @param <T>
* @param source
* Source array to scroll.
* @param first
* The index of element which will be first.
*
* @return reference to the source array.
*/
public static<T> T[] scroll(T[] source, int first)
{
if (first <= 0 || first >= source.length - 1)
return source;
T[] tmp1 = Arrays.copyOfRange(source, 0, first);
T[] tmp2 = Arrays.copyOfRange(source, first, source.length);
for (int i = 0; i < source.length; i++)
{
source[i] = (i < (source.length - first)) ? tmp2[i] :
tmp1[i - (source.length - first)];
}
return source;
}
/**
* Convert the array of wrapper objects into the corresponding array of
* primitive types.
*
* @param array
* The array of wrapper objects. Must not be <code>null</code>.
*
* @return The corresponding primitive array.
*/
public static int[] objectToPrimitive(Integer[] array)
{
int[] out = new int[array.length];
for (int i = 0; i < array.length; i++)
{
out[i] = array[i].intValue();
}
return out;
}
/**
* Convert the array of primitives into the corresponding array of wrapper
* objects.
*
* @param array
* The array of primitive types. Must not be <code>null</code>.
*
* @return The corresponding wrapper object array.
*/
public static Integer[] primitiveToObject(int[] array)
{
Integer[] out = new Integer[array.length];
for (int i = 0; i < array.length; i++)
{
out[i] = Integer.valueOf(array[i]);
}
return out;
}
/**
* Convert a collection of wrapper objects into the corresponding array of
* primitive types.
*
* @param list
* The collection of wrapper objects. Must not be
* <code>null</code>.
*
* @return The corresponding primitive array.
*/
public static int[] integerCollectionToPrimitive(Collection<Integer> list)
{
return objectToPrimitive(list.toArray(new Integer[list.size()]));
}
/**
* Convert a map of wrapper objects into the corresponding array of
* primitive types.
*
* @param map
* The map of wrapper objects. Must not be
* <code>null</code>.
*
* @return The corresponding primitive arrays. Array on index 0 will hold
* the keys and array on index 1 will hold the values.
*/
public static int[][] integerMapToPrimitive(Map<Integer, Integer> map)
{
int[][] res = new int[2][map.size()];
int idx = 0;
for (int key : map.keySet())
{
res[0][idx] = key;
res[1][idx] = map.get(key);
idx++;
}
return res;
}
/**
* Convert an array of primitive types into a list of the corresponding
* wrapper objects.
*
* @param array
* The primitive array. Must not be <code>null</code>.
*
* @return The list of wrapper objects.
*/
public static List<Integer> primitiveToIntegerList(int[] array)
{
return Arrays.asList(primitiveToObject(array));
}
/**
* Check if a specific network end point can be connected from the local host.
*
* @param host
* The given resolved host
* @param port
* The target port number on the given host to check for availability
* @param timeout
* The connection timeout in milliseconds
*
* @return True if the port is listened on the remote host, otherwise false
*
* @throws SocketTimeoutException
* It happens if timeout expires before connecting test completed
*/
public static boolean isEndPointAvailable(InetAddress host, int port, int timeout)
throws SocketTimeoutException
{
try(java.net.Socket sock = new java.net.Socket())
{
sock.setReuseAddress(true);
sock.setSoLinger(false, 0);
sock.setTcpNoDelay(true);
sock.connect(new InetSocketAddress(host, port), timeout);
return true;
}
catch(SocketTimeoutException ex0)
{
throw ex0;
}
catch (IOException ex1)
{
LOG.log(Level.WARNING, "not available host:port %s:%d.", host, port);
}
return false;
}
/**
* Test if a specific network host name or IP address represents a network interface.
*
* @param host
* The target network host name or IP address to check
*
* @return True if the host represents a network interface, otherwise false
*/
public static boolean isValidHostNameOrIpAddress(String host)
{
try
{
InetAddress addr = InetAddress.getByName(host);
if (addr.isLoopbackAddress() || addr.isAnyLocalAddress())
{
return false;
}
return NetworkInterface.getByInetAddress(addr) != null;
}
catch(Throwable e)
{
return false;
}
}
/**
* Convert the array of wrapper objects into the corresponding array of
* primitive types.
*
* @param array
* The array of wrapper objects. Must not be <code>null</code>.
*
* @return The corresponding primitive array.
*/
public static long[] objectToPrimitive(Long[] array)
{
long[] out = new long[array.length];
for (int i = 0; i < array.length; i++)
{
out[i] = array[i].longValue();
}
return out;
}
/**
* Convert the array of primitives into the corresponding array of wrapper
* objects.
*
* @param array
* The array of primitive types. Must not be <code>null</code>.
*
* @return The corresponding wrapper object array.
*/
public static Long[] primitiveToObject(long[] array)
{
Long[] out = new Long[array.length];
for (int i = 0; i < array.length; i++)
{
out[i] = Long.valueOf(array[i]);
}
return out;
}
/**
* Convert a collection of wrapper objects into the corresponding array of
* primitive types.
*
* @param list
* The collection of wrapper objects. Must not be
* <code>null</code>.
*
* @return The corresponding primitive array.
*/
public static long[] longCollectionToPrimitive(Collection<Long> list)
{
return objectToPrimitive(list.toArray(new Long[list.size()]));
}
/**
* Convert an array of primitive types into a list of the corresponding
* wrapper objects.
*
* @param array
* The primitive array. Must not be <code>null</code>.
*
* @return The list of wrapper objects.
*/
public static List<Long> primitiveToLongList(long[] array)
{
return Arrays.asList(primitiveToObject(array));
}
/**
* Load and return a <code>Properties</code> object from a properties
* file specified by <code>filename</code>.
*
* @param filename
* Name of the properties file.
*
* @return Properties object containing the data read from
* <code>filename</code>.
*
* @throws IOException
* if any error occurs reading the file.
*/
public static Properties loadProperties(String filename)
throws IOException
{
Properties props = new Properties();
FileInputStream fis = null;
BufferedInputStream bis = null;
try
{
// Read properties from file.
fis = new FileInputStream(filename);
bis = new BufferedInputStream(fis);
props.load(bis);
}
finally
{
// Clean up streams, regardless of success.
if (bis != null)
{
bis.close();
}
else if (fis != null)
{
fis.close();
}
}
return props;
}
/**
* Retrieve the property with the specified <code>key</code> from within
* the specified properties. Assume <code>key</code> represents a required
* property; an exception is thrown if it is not present.
*
* @param props
* Properties object from which to retrieve required property.
* @param key
* Key to the required property.
*
* @return property value.
*
* @throws MissingDataException
* if the required property is not present.
*/
public static String getRequiredProperty(Properties props, String key)
throws MissingDataException
{
String value = props.getProperty(key);
if (value == null)
{
throw new MissingDataException("'" + key + "' is a required configuration property");
}
return value;
}
/**
* Displays a prompt on <code>stdout</code> and returns the input entered
* by the user on <code>stdin</code>. The first new line encountered
* will end the input reading loop and the text will not include this
* new line character.
*
* @param txt
* The text message to display.
*
* @return The text that was interactively entered or <code>null</code>
* if there was no input provided.
*
* @throws IOException
*/
public static char[] prompt(String txt)
throws IOException
{
System.out.print(txt);
System.out.flush();
StringBuilder sb = new StringBuilder();
int ch;
readLoop:
while (true)
{
switch (ch = System.in.read())
{
case -1:
case '\n':
break readLoop;
case '\f':
case '\t':
case '\r':
continue readLoop;
default:
sb.append((char) ch);
break;
}
}
if (sb.length() == 0)
{
return null;
}
return sb.toString().toCharArray();
}
/**
* Determine if the given throwable or one of its causes matches one of the throwable
* types given as criteria.
*
* @param thr
* Throwable from which to examine cause chain.
* @param criteria
* List of throwables to check for.
*
* @return <code>true</code> if any of the criteria throwables matches the given
* throwable or any throwable in the cause chain.
*/
public static boolean causeChainContains(Throwable thr, Class[] criteria)
{
Throwable next = thr;
do
{
for (int i = 0; i < criteria.length; i++)
{
if (criteria[i].isInstance(next))
{
return true;
}
}
next = next.getCause();
}
while (next != null);
return false;
}
/**
* Reports the default character set name. Since the Progress default
* charset is ISO-8859-1, when the JVM reports that UTF-8 is the default,
* this method will return the string "ISO-8859-1" instead.
* <p>
* The assumption (possibly not always correct) is that UTF-8 is not a
* valid Progress charset and instead, to provide a proper default, we
* must override with a compatible charset. Note that if the default
* charset name is not UTF-8, it is assumed that the charset has been
* overridden already (at JVM startup). In that case, the default is
* returned unchanged.
*
* @return A default charset name.
*/
public static String getCharsetName()
{
return getCharsetOverride();
}
/**
* Overrides the default character set by the Progress default charset ISO-8859-1
* if UTF-8 is the default JVM charset, otherwise returns the JVM default charset.
* <p>
* If the 4GL environment is actually configured for UTF-8, then this approach won't work.
* If the default codepage name for the JVM is not UTF-8, it is assumed that the codepage
* has been overriden already (at JVM startup). In that case, the value reported by the JVM
* is returned unchanged.
*
* @return The JVM default charset if no override is required.
*/
public static String getCharsetOverride()
{
Charset def = Charset.defaultCharset();
return getCharsetOverride(def.name());
}
/**
* Overrides the given UTF-8 charset by the Progress default charset ISO-8859-1, otherwise
* returns the given charset unchanged.
*
* @param charsetName
* Charset name to override
*
* @return The given charset name if no override is required.
*/
public static String getCharsetOverride(String charsetName)
{
String cset;
// this test may not be complete, but it handles the most common
// case where the extended ASCII range will be "munged" by an
// improper byte to char conversion
if ("UTF-8".equalsIgnoreCase(charsetName))
{
// this gets called too much to allow it to log since we don't want it cluttering
// client or conversion logs
// token = UnimplementedFeature.todo("This code doesn't properly handle UTF-8.", token);
// override with a safe default (matches the Progress default)
cset = "ISO-8859-1";
}
else
{
cset = charsetName;
}
return cset;
}
/**
* Convert the given byte to a Unicode character using the given character
* set (or the default charset if the given charset name is
* <code>null</code>).
* <p>
* The normal ASCII range can be converted to a Unicode character by a
* simple widening conversion from byte to char. But the extended ASCII
* range (128 through 255 inclusive) will translate to character values
* that cannot be achieved via a widening conversion. Instead the
* proper character set must be used to get the right result.
*
* @param b
* The byte to convert.
* @param cset
* The charset name to use for conversion or <code>null</code>
* if the default charset should be used.
*
* @return The Unicode character that represents the given byte in the
* specified character set. In case of any error, -1 will be
* returned.
*/
public static char toChar(byte b, String cset)
{
String txt = null;
char ch = (char) -1;
try
{
if (cset == null)
{
// use the default character set to translate this byte
// into a character
txt = new String(new byte[] {b});
}
else
{
// use a specific character set to convert rather than the
// default
txt = new String(new byte[] {b}, cset);
}
ch = txt.charAt(0);
}
catch (UnsupportedEncodingException uee)
{
// nothing to do
}
return ch;
}
/**
* Convert the given byte to a Unicode character using the given character
* set (or the default charset if the given charset name is
* <code>null</code>).
* <p>
* <b>Only the least significant byte will be converted, all other data is
* dropped at this time.</b>
* <p>
* The normal ASCII range can be converted to a Unicode character by a
* simple widening conversion from byte to char. But the extended ASCII
* range (128 through 255 inclusive) will translate to character values
* that cannot be achieved via a widening conversion. Instead the
* proper character set must be used to get the right result.
*
* @param keyCode
* The number to convert. Must be between 0 and 255 inclusive.
* @param cset
* The charset name to use for conversion or <code>null</code>
* if the default charset should be used.
*
* @return The Unicode character that represents the given byte in the
* specified character set. In case of any error, -1 will be
* returned.
*/
public static char toChar(int keyCode, String cset)
{
try
{
int codepoint = getCodepoint(keyCode, cset);
if (codepoint == -1)
{
codepoint = keyCode;
}
return toChar(codepoint);
}
catch (IllegalArgumentException ex)
{
// no-op
}
return (char) -1;
}
/**
* Convert the unicode code point to character.
*
* @param codepoint
* The unicode code point.
*
* @return The Unicode character that represents the given code point
*/
public static char toChar(int codepoint)
{
try
{
if (codepoint != -1)
{
char[] chars = Character.toChars(codepoint);
if (chars.length == 1)
{
return chars[0];
}
}
}
catch (IllegalArgumentException ex)
{
// no-op
}
return (char) -1;
}
/**
* Convert the given Unicode character to an integer value using the given
* character set (or the default charset if the given charset name is
* <code>null</code>).
* <p>
* The normal ASCII range can be converted from a Unicode character by a
* simple narrowing conversion from char to byte. But the Unicode
* characters representing the extended ASCII range (128 through 255
* inclusive) are not represented in Unicode via a single byte. So a
* translation from character values to a number cannot be achieved via a
* narrowing conversion. Instead the proper character set must be used to
* get the right result.
*
* @param ch
* The Unicode character to convert.
* @param cset
* The charset name to use for conversion or <code>null</code>
* if the default charset should be used.
*
* @return The integer value that represents the given the Unicode
* character in the specified character set or -1 on any error.
*/
public static int toInt(char ch, String cset)
{
int i = -1;
try
{
String encoded = new String(new char[] {ch});
byte[] b = (cset == null) ? encoded.getBytes() : encoded.getBytes(cset);
i = b[0] & 0x000000FF;
}
catch (UnsupportedEncodingException uee)
{
// let -1 be returned
}
return i;
}
/**
* Convert a unicode codepoint into codepoint according to the provided charset.
*
* @param codepoint
* The given unicode codepoint
* @param cset
* The target charset name
*
* @return The integer code representing the given unicode codepoint in the target charset.
*
* @throws CharacterCodingException
* If the given unicode codepoint cannot be represented by the target charset
* @throws UnsupportedCharsetException
* If the target charset is not supported.
*/
public static int convertCodepoint(int codepoint, String cset)
throws CharacterCodingException, UnsupportedCharsetException
{
char[] keyChars = Character.toChars(codepoint);
Charset charSet = Charset.forName(cset);
CharsetEncoder encoder = charSet.newEncoder();
encoder.onMalformedInput(CodingErrorAction.REPORT);
encoder.onUnmappableCharacter(CodingErrorAction.REPORT);
ByteBuffer byteBuffer = encoder.encode(CharBuffer.wrap(keyChars));
byte[] keyBytes = byteBuffer.array();
int len = keyBytes.length;
int len1 = len - 1;
for(int i = len - 1; i >= 0; i--)
{
if (keyBytes[i] != 0)
{
len1 = i;
break;
}
}
int ret = 0;
for (int i = 0; i <= len1; i++)
{
int bt = Byte.toUnsignedInt(keyBytes[i]);
//System.out.println(Integer.toString(bt, 16));
ret = ret | bt;
if (i < len1)
{
ret = ret << 8;
}
}
return ret;
}
/**
* Get the unicode codepoint for the given key code and char set.
*
* @param keyCode
* The given key code
* @param cset
* The given char set.
*
* @return The unicode codepoint if keycode is a valid key code in the given char set, otherwise -1.
*/
public static int getCodepoint(int keyCode, String cset)
{
ByteBuffer bf = ByteBuffer.allocate(4);
bf.putInt(keyCode);
bf.flip();
try
{
for (int i = 0; i < 4; i++)
{
if (bf.get(i) != 0)
{
bf.position(i);
break;
}
}
byte[] buf = new byte[bf.limit() - bf.position()];
for (int i = bf.position(), j = 0; i < bf.limit(); i++, j++)
{
buf[j] = bf.get(i);
}
Charset charSet = Charset.forName(cset);
CharsetDecoder decoder = charSet.newDecoder();
decoder.onMalformedInput(CodingErrorAction.REPORT);
decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
CharBuffer charBuffer = decoder.decode(ByteBuffer.wrap(buf));
String key = new String(charBuffer.array());
if (key.length() == 1)
{
return Character.codePointAt(key, 0);
}
}
catch (IllegalArgumentException | CharacterCodingException e)
{
}
return -1;
}
/**
* Creates all subdirectories needed to provide a valid path for the
* target filename that is passed in. It is expected that there will
* always be a path included in this filename such that making
* directories makes sense.
*
* @param tar
* Target filename for which paths must be created.
*
* @return <code>true</code> if the full necessary path already
* exists or if it has been created, <code>false</code>
* otherwise.
*/
public static boolean createPathForTarget(String tar)
{
String path = tar.substring(0, tar.lastIndexOf(File.separatorChar));
File file = new File(path);
// anything to do?
if (file.exists() && file.isDirectory())
return true;
try
{
// best effort to create the directories including all needed parent dirs
file.mkdirs();
}
catch (Exception exc)
{
// TODO: add logging
// some failure, probably a security violation
return false;
}
// it better be there now...
if (file.exists() && file.isDirectory())
return true;
// some other unexpected problem!
return false;
}
/**
* Find the specified file in the file system using the specified search
* paths and return it. Handles absolute and relative paths.
*
* @param filename
* Name of the source file to find; if this is not an absolute
* path, it should be relative to one of the paths in the
* <code>searchPaths</code> array.
* @param paths
* Array of absolute paths which are prepended to a relative
* <code>filename</code>.
*
* @return The file if it was found.
*
* @throws FileNotFoundException
* if a file named <code>filename</code> does not exist in the
* specified search paths (or at the absolute path provided).
*/
public static File findFileInPath(String filename, String[] paths)
throws FileNotFoundException
{
File file = new File(filename);
if (file.isAbsolute())
{
// Return file if it is absolute and exists.
if (file.exists())
{
return file;
}
}
else
{
// Use search paths to resolve relative references.
int len = paths.length;
for (int i = 0; i < len; i++)
{
file = new File(paths[i], filename);
if (file.exists())
{
return file;
}
}
}
// Reassemble path for error message.
StringBuilder buf = new StringBuilder();
String sep = File.pathSeparator;
int len = paths.length;
for (int i = 0; i < len; i++)
{
buf.append(paths[i])
.append(sep);
}
throw new FileNotFoundException("File '" + filename + "' not found in path: " + buf);
}
/**
* Calculates a resource path to the target page within its application class path.
*
* @param pageContext
* The page root path
* @param location
* The package where the target resource is located.
* @param pageFile
* The resource name with its extension.
*
* @return The string path to be found within its application class path.
*/
public static String calculateResourcePath(String pageContext,
Package location,
String pageFile)
{
StringBuilder sb = new StringBuilder(pageContext);
String packageName = location.getName();
sb.append(packageName.replace('.', '/'));
if (!pageFile.startsWith("/"))
{
sb.append("/");
}
return sb.append(pageFile).toString();
}
/**
* Canonicalize the given file system path as described by
* <code>java.io.File.getCanonicalPath()</code>. In addition, normalize
* the use of the file separator by replacing all instances of
* <code>oldSeparator</code> with <code>newSeparator</code>.
*
* @param path
* A relative or absolute path to be canonicalized.
* @param oldSeparator
* File separator to be replaced.
* @param newSeparator
* New file separator.
*
* @return Canonicalized path containing normalized file separators.
*
* @throws IOException
* If there is a file system while canonicalizing the path.
*/
public static String canonicalizePath(String path, String oldSeparator, String newSeparator)
throws IOException
{
String res = null;
try
{
res = new File(path).getCanonicalPath();
}
catch (IOException e)
{
// On windows trying to access some path could throw exceptions like these:
// TODO: identify correct fix and remove this line
LOG.warning("", e);
res = path; // consider path as already canonical (or return an invalid value ?)
}
res = replaceSeparator(res, oldSeparator, newSeparator);
return res;
}
/**
* Returns URI based on the file schema and the given path.
*
* @param path
* The given path
*
* @return The URI based on the file path
* @throws URISyntaxException
* If the given path cannot be transformed to URI.
*/
public static URI toURI(String path)
throws URISyntaxException
{
return new URI("file", null, path, null, null);
}
/**
* Replace all instances of {@code oldSeparator} with {@code newSeparator} in the given path.
*
* @param path
* A file path.
* @param oldSeparator
* File separator to be replaced.
* @param newSeparator
* New file separator.
*
* @return Path containing normalized file separators.
*/
public static String replaceSeparator(String path, String oldSeparator, String newSeparator)
{
if (!oldSeparator.equals(newSeparator))
{
if ("\\".equals(oldSeparator))
{
oldSeparator = "\\\\";
}
if ("\\".equals(newSeparator))
{
newSeparator = "\\\\";
}
path = path.replaceAll(oldSeparator, newSeparator);
}
return path;
}
/**
* Determines if a both source and target exist as files and if the target
* has a later timestamp. It does not consider contents or size in this
* determination.
*
* @param source
* Source filename.
* @param target
* Target filename.
*
* @return <code>true</code> if the both files exist and the target is
* newer, otherwise <code>false</code> (includes failures).
*/
public static boolean isLaterVersion(String source, String target)
{
File src = new File(source);
File tar = new File(target);
// make sure the source file is OK
boolean srcOK = src.exists() && src.isFile();
if (!srcOK)
return false;
// make sure the target file is not there or if it is there that
// it is older than the source
if (tar.exists())
{
if (tar.isFile())
{
// returns true if the source is older, false if the target is
// older
return src.lastModified() < tar.lastModified();
}
}
return false;
}
/**
* Creates the directory tree to the target if needed and then copies
* the source to the target if needed. The copy is byte for byte, no
* modifications of the file will occur.
*
* @param src
* Source filename.
* @param tar
* Target filename.
*
* @return <code>true</code> if the file was copied successfully OR
* if the file didn't need to be copied, <code>false</code>
* on any failure.
*/
public static boolean simpleCopy(String src, String tar)
{
// try to create the directory
if (!createPathForTarget(tar))
{
// dir creation failed
return false;
}
return copyFileIfNeeded(src, tar, 0);
}
/**
* Conditionally copies a file, byte for byte (no changes) to a new file,
* if and only if the source is an accessible file and the target doesn't
* exist or the target exists as a file but is older than the source or
* has a different size.
*
* @param source
* Source filename.
* @param target
* Target filename.
* @param bsize
* Buffer size or if 0 the buffer size will default to the file
* size or 64Kb whichever is less.
*
* @return <code>true</code> if the file was copied successfully OR if
* the file didn't need to be copied, <code>false</code> on any
* failure.
*/
public static boolean copyFileIfNeeded(String source, String target, int bsize)
{
File src = new File(source);
File tar = new File(target);
// make sure the source file is OK
boolean srcOK = src.exists() && src.isFile();
if (!srcOK)
return false;
boolean tarOK = true;
// make sure the target file is not there or if it is there that
// it is older than the source or it is not the same size, before
// trying to copy
if (tar.exists())
{
if (tar.isFile())
{
boolean oldSrc = src.lastModified() > tar.lastModified();
long diff = src.length() - tar.length();
tarOK = oldSrc || (diff != 0);
}
else
{
return false;
}
}
boolean result = false;
// already checked src, now check target
if (tarOK)
{
// best efforts attempt to copy
result = copyFile(source, target, 0, false);
}
return result;
}
/**
* Handles the byte for byte copying of a single source file to a target
* file, using a buffer size of the caller's choice. The resulting target
* file will be a newly created file (date/timestamps will be new).
*
* @param source
* Source filename.
* @param target
* Target filename.
* @param bsize
* Buffer size or if 0 the buffer size will default to the file
* size or 64Kb whichever is less.
* @param append
* <code>true</code> if the output file should be opened in
append mode.
*
* @return <code>true</code> if the file was copied successfully,
* <code>false</code> on any failure.
*/
public static boolean copyFile(String source, String target, int bsize, boolean append)
{
File in = new File(source);
long len = in.length();
// if an invalid buffer size is set, the lesser of 64Kb or the file
// length is used
if (bsize < 1)
{
bsize = 64*1024;
if (len < bsize)
bsize = (int) len;
}
try
{
FileInputStream src = new FileInputStream(in);
FileOutputStream tar = new FileOutputStream(target, append);
int off = 0;
byte[] buf = new byte[bsize];
while (len > bsize)
{
src.read(buf, off, bsize);
tar.write(buf, 0, bsize);
len -= bsize;
}
src.read(buf, off, (int)len);
tar.write(buf, 0, (int)len);
src.close();
tar.close();
}
catch (Exception exc)
{
return false;
}
return true;
}
/**
* Supply a list of files which case-insensitively match the given file
* name. On a case-insensitive file system, this list will contain no more
* than one match. However, on a case-sensitive file system, the list may
* contain more matches.
*
* @param path
* The file name to be matched. It will be canonicalized before
* the search is conducted.
* @param separator
* Legacy system file separator which will be replaced with the
* target system's file separator (in the result list).
*
* @return A list containing 0 or more files. This method will never
* return <code>null</code>.
*
* @throws IOException
* if there is any error canonicalizing the <code>path</code>.
*/
public static List<File> getCaseInsensitiveFilenameMatches(String path, String separator)
throws IOException
{
// canonicalize path and ensure file separators are appropriate for the
// current platform's file system
String canonicalPath = canonicalizePath(path, separator, File.separator);
return getCaseInsensitiveFilenameMatches(canonicalPath);
}
/**
* Supply a list of files which case-insensitively match the given file
* name. On a case-insensitive file system, this list will contain no more
* than one match. However, on a case-sensitive file system, the list may
* contain more matches.
*
* @param path
* The file name to be matched. It must already be canonicalized
* (see {@link #canonicalizePath}) and with all file separators
* converted to the current system's file separator character.
*
* @return A list containing 0 or more files. This method will never
* return <code>null</code>.
*
* @throws IOException
* if there is any error canonicalizing the <code>path</code>.
*/
public static List<File> getCaseInsensitiveFilenameMatches(String path)
throws IOException
{
List<File> list = new ArrayList<>(1);
matchPathCaseInsensitively(null, path, list);
return list;
}
/**
* Get a string which uniquely describes the current context. This is
* intended primarily for debugging or logging purposes.
*
* @return Description of context.
*/
public static String describeContext()
{
SecurityManager security = SecurityManager.getInstance();
if (security == null || !security.isServer())
{
return Thread.currentThread().getName();
}
StringBuilder buf = new StringBuilder();
// Session ID.
buf.append(String.format("%08X", security.sessionSm.getSessionId()));
buf.append(":");
// Thread ID.
Object tid = security.getThreadId();
if (tid != null)
{
buf.append(String.format("%08X", tid));
}
else
{
buf.append("????????");
}
buf.append(":");
// User ID.
buf.append(security.getUserId());
return buf.toString();
}
/**
* Invoke the specified {@link Method} using ReflectASM instead of standard reflection.
* <p>
* ReflectASM related data is cached in {@link #METHOD_DATA}, thread-local, to avoid the concurrent access.
* More, the maps are identity-keyed, for fast access.
*
* @param method
* The method to execute.
* @param instance
* The instance on which the call will be made; can be <code>null</code> for static methods.
* @param args
* The method's arguments.
*
* @return The method's return value.
*
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvocationTargetException
*/
public static Object invoke(Method method, Object instance, Object... args)
throws IllegalAccessException,
IllegalArgumentException,
InvocationTargetException
{
if (instance == null && !Modifier.isStatic(method.getModifiers()))
{
throw new NullPointerException("NULL instance used when invoking method " + method.toString() +
" in " + method.getDeclaringClass());
}
Class<?> cls = method.getDeclaringClass();
MethodData mdata = METHOD_DATA.get(cls);
if (mdata == null)
{
METHOD_DATA.put(cls, mdata = new MethodData(cls));
}
Integer accessIdx = mdata.methodAccessIdx.get(method);
return mdata.access.invoke(instance, accessIdx, args);
// return method.invoke(instance, args);
}
/**
* Executes the given entry point using reflection, instantiating
* the target class first, if necessary.
* Catches the exceptions and rethrows those of them that represent
* Progress conditions.
*
* @param cl
* The class to be instantiated. Must be public. Must have a
* public, default constructor, unless <code>entry</code> is a
* static method.
* @param entry
* The method to be executed. Must exist within <code>cl</code>.
* May be an instance method or static method; must be
* publicly accessible.
* @param args
* If the method's signature contains parameters, this is a
* variable argument list containing those parameters.
*
* @return The result of the invocation. If the signature of the method
* specifies a <code>void</code> return then this will return
* <code>null</code>. Note that it is a limitation of the
* design of Java method signatures such that we do not
* distinguish between a method with a <code>void</code> return
* and a method that has a real return value which is set to
* <code>null</code>.
* @throws NullPointerException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvocationTargetException
* @throws InstantiationException
*/
public static Object invoke(Class<?> cl, Method entry, Object... args)
throws NullPointerException,
IllegalAccessException,
IllegalArgumentException,
ReflectiveOperationException,
InstantiationException
{
// simple test for a quick out
if (entry == null)
{
throw new NullPointerException("Specified entry point is null!");
}
Object result = null;
try
{
Object instance = null;
// instantiate the class (for non-static method)
if ((entry.getModifiers() & Modifier.STATIC) == 0)
{
String pname = SourceNameMapper.getLegacySourceName(cl.getName());
ProcedureManager.setInstantingExternalProgram(pname);
ProcedureManager.setInstantingExternalProgramClass(cl);
try
{
instance = cl.getDeclaredConstructor().newInstance();
}
catch (ErrorConditionException ece)
{
String msg = ece.getMessage();
int num = ece.getProgressErrorCode();
ErrorManager.recordOrThrowError(num, msg, false);
}
finally
{
ProcedureManager.setInstantingExternalProgram(null);
ProcedureManager.setInstantingExternalProgramClass(null);
}
}
// invoke the method
result = entry.invoke(instance, args);
}
catch (InvocationTargetException exc)
{
// if the exception thrown was a P2J runtime type, we
// must handle it normally, so extract the root cause
// exception and rethrow it
Throwable cause = exc.getCause();
if (cause != null)
{
if (cause instanceof ConditionException)
{
throw (ConditionException) cause;
}
if (cause instanceof RetryUnwindException)
{
throw (RetryUnwindException) cause;
}
// exception is a runtime problem that we don't handle here
throw exc;
}
}
return result;
}
/**
* Verify that a directory node at a particular, required directory path
* exists, and that the class of the node found at that path is that which
* is expected.
*
* @param ds
* Directory service.
* @param id
* ID of node to verify.
* @param expected
* Expected class of node.
*
* @throws RuntimeException
* if directory node does not exist or is of the incorrect class.
*/
public static void verifyDirectoryNode(DirectoryService ds, String id, String expected)
throws RuntimeException
{
String nodeClass = ds.getNodeClass(id);
if (nodeClass == null)
{
throw new RuntimeException("Directory entry missing: " + id);
}
if (!nodeClass.equals(expected))
{
throw new RuntimeException("Directory entry is incorrect type: " + id);
}
}
/**
* Obtain the specified attribute from the directory if it exists or return
* the default value if the directory node doesn't exist with the proper
* type, the directory paths don't exist or the directory is unavailable.
* <p>
* This method implements a search algorithm that allows values to be
* found that are account (user or process) specific or group specific
* within the current server or a global default for all servers.
* <p>
* The implementation iteratively looks up the directory node under:
* /server/<serverID>/runtime/<account_or_group>/<id>
* <p>
* If no user/process or group nodes are present, then this is checked:
* /server/<serverID>/runtime/default/<id>
* <p>
* If no /server/<serverID>/runtime node exists, this is checked
* (it is the global default area for all servers):
* /server/default/runtime/<account_or_group>/<id>
* <p>
* Finally, if no user/process or group nodes are present in the global
* default area, then this is checked:
* /server/default/runtime/default/<id>
* <p>
* No exceptions are thrown, the given initial value will always be safely
* returned.
*
* @param ds
* A bound directory service instance. If this is
* <code>null</code> then the instance will be dynamically
* queried and bound for the duration of the method and then
* unbound at the end. This is a convenience for situations
* in which only a single value is needed. This has negative
* performance implications in the case where multiple values
* are sequentially obtained.
* @param nodeId
* ID of node to obtain. This will be appended to the various
* paths listed above (and in the precedence order specified
* above).
* @param initial
* The default value to return if the directory backed value
* cannot be obtained for ANY reason.
*
* @return The requested value or the given initial value if for ANY
* reason the directory backed value cannot be obtained.
*/
public static int getDirectoryNodeInt(DirectoryService ds, String nodeId, int initial)
{
return getDirectoryNodeInt(ds, nodeId, initial, DirScope.ACCOUNTS, null);
}
/**
* Obtain the specified attribute from the directory if it exists or return
* the default value if the directory node doesn't exist with the proper
* type, the directory paths don't exist or the directory is unavailable.
* <p>
* This method implements a search algorithm that allows values to be
* found that are account (user or process) specific or group specific
* within the current server or a global default for all servers.
* <p>
* The implementation iteratively looks up the directory node under:
* /server/<serverID>/runtime/<account_or_group>/<id>
* <p>
* If no user/process or group nodes are present, then this is checked:
* /server/<serverID>/runtime/default/<id>
* <p>
* If no /server/<serverID>/runtime node exists, this is checked
* (it is the global default area for all servers):
* /server/default/runtime/<account_or_group>/<id>
* <p>
* Finally, if no user/process or group nodes are present in the global
* default area, then this is checked:
* /server/default/runtime/default/<id>
* <p>
* No exceptions are thrown, the given initial value will always be safely
* returned.
*
* @param ds
* A bound directory service instance. If this is
* <code>null</code> then the instance will be dynamically
* queried and bound for the duration of the method and then
* unbound at the end. This is a convenience for situations
* in which only a single value is needed. This has negative
* performance implications in the case where multiple values
* are sequentially obtained.
* @param nodeId
* ID of node to obtain. This will be appended to the various
* paths listed above (and in the precedence order specified
* above).
* @param initial
* The default value to return if the directory backed value
* cannot be obtained for ANY reason.
*
* @return The requested value or the given initial value if for ANY
* reason the directory backed value cannot be obtained.
*/
public static double getDirectoryNodeDouble(DirectoryService ds, String nodeId, double initial)
{
return getDirectoryNodeDouble(ds, nodeId, initial, DirScope.ACCOUNTS, null);
}
/**
* Obtain the specified attribute from the directory if it exists or return
* the default value if the directory node doesn't exist with the proper
* type, the directory paths don't exist or the directory is unavailable.
* <p>
* This method implements a search algorithm that allows values to be
* found that are account (user or process) specific or group specific
* within the current server or a global default for all servers.
* <p>
* The implementation iteratively looks up the directory node under:
* /server/<serverID>/runtime/<account_or_group>/<id>
* <p>
* If no user/process or group nodes are present, then this is checked:
* /server/<serverID>/runtime/default/<id>
* <p>
* If no /server/<serverID>/runtime node exists, this is checked
* (it is the global default area for all servers):
* /server/default/runtime/<account_or_group>/<id>
* <p>
* Finally, if no user/process or group nodes are present in the global
* default area, then this is checked:
* /server/default/runtime/default/<id>
* <p>
* No exceptions are thrown, the given initial value will always be safely
* returned.
*
* @param ds
* A bound directory service instance. If this is
* <code>null</code> then the instance will be dynamically
* queried and bound for the duration of the method and then
* unbound at the end. This is a convenience for situations
* in which only a single value is needed. This has negative
* performance implications in the case where multiple values
* are sequentially obtained.
* @param nodeId
* ID of node to obtain. This will be appended to the various
* paths listed above (and in the precedence order specified
* above).
* @param initial
* The default value to return if the directory backed value
* cannot be obtained for ANY reason.
*
* @return The requested value or the given initial value if for ANY
* reason the directory backed value cannot be obtained.
*/
public static char getDirectoryNodeChar(DirectoryService ds, String nodeId, char initial)
{
return getDirectoryNodeChar(ds, nodeId, initial, DirScope.ACCOUNTS, null);
}
/**
* Obtain the specified attribute from the directory if it exists or return
* the default value if the directory node doesn't exist with the proper
* type, the directory paths don't exist or the directory is unavailable.
* <p>
* This method implements a search algorithm that allows values to be
* found that are account (user or process) specific or group specific
* within the current server or a global default for all servers.
* <p>
* The implementation iteratively looks up the directory node under:
* /server/<serverID>/runtime/<account_or_group>/<id>
* <p>
* If no user/process or group nodes are present, then this is checked:
* /server/<serverID>/runtime/default/<id>
* <p>
* If no /server/<serverID>/runtime node exists, this is checked
* (it is the global default area for all servers):
* /server/default/runtime/<account_or_group>/<id>
* <p>
* Finally, if no user/process or group nodes are present in the global
* default area, then this is checked:
* /server/default/runtime/default/<id>
* <p>
* No exceptions are thrown, the given initial value will always be safely
* returned.
*
* @param ds
* A bound directory service instance. If this is
* <code>null</code> then the instance will be dynamically
* queried and bound for the duration of the method and then
* unbound at the end. This is a convenience for situations
* in which only a single value is needed. This has negative
* performance implications in the case where multiple values
* are sequentially obtained.
* @param nodeId
* ID of node to obtain. This will be appended to the various
* paths listed above (and in the precedence order specified
* above).
* @param initial
* The default value to return if the directory backed value
* cannot be obtained for ANY reason.
*
* @return The requested value or the given initial value if for ANY
* reason the directory backed value cannot be obtained.
*/
public static String getDirectoryNodeString(DirectoryService ds, String nodeId, String initial)
{
return getDirectoryNodeString(ds, nodeId, initial, DirScope.ACCOUNTS, null);
}
/**
* Obtain the specified attribute from the directory if it exists or return
* the default value if the directory node doesn't exist with the proper
* type, the directory paths don't exist or the directory is unavailable.
* <p>
* This method implements a search algorithm that allows values to be
* found that are account (user or process) specific or group specific
* within the current server or a global default for all servers.
* <p>
* The implementation iteratively looks up the directory node under:
* /server/<serverID>/runtime/<account_or_group>/<id>
* <p>
* If no user/process or group nodes are present, then this is checked:
* /server/<serverID>/runtime/default/<id>
* <p>
* If no /server/<serverID>/runtime node exists, this is checked
* (it is the global default area for all servers):
* /server/default/runtime/<account_or_group>/<id>
* <p>
* Finally, if no user/process or group nodes are present in the global
* default area, then this is checked:
* /server/default/runtime/default/<id>
* <p>
* No exceptions are thrown, the given initial value will always be safely
* returned.
*
* @param ds
* A bound directory service instance. If this is
* <code>null</code> then the instance will be dynamically
* queried and bound for the duration of the method and then
* unbound at the end. This is a convenience for situations
* in which only a single value is needed. This has negative
* performance implications in the case where multiple values
* are sequentially obtained.
* @param nodeId
* ID of node to obtain. This will be appended to the various
* paths listed above (and in the precedence order specified
* above).
* @param initial
* The default value to return if the directory backed value
* cannot be obtained for ANY reason.
*
* @return The requested value or the given initial value if for ANY
* reason the directory backed value cannot be obtained.
*/
public static boolean getDirectoryNodeBoolean(DirectoryService ds,
String nodeId,
boolean initial)
{
return getDirectoryNodeBoolean(ds, nodeId, initial, DirScope.ACCOUNTS, null);
}
/**
* Obtain the specified attribute from the directory if it exists or return
* the default value if the directory node doesn't exist with the proper
* type, the directory paths don't exist or the directory is unavailable.
* <p>
* This method implements a search algorithm that allows values to be
* found that are account (user or process) specific or group specific
* within the current server or a global default for all servers.
* <p>
* The implementation iteratively looks up the directory node under:
* /server/<serverID>/runtime/<account_or_group>/<id>
* <p>
* If no user/process or group nodes are present, then this is checked:
* /server/<serverID>/runtime/default/<id>
* <p>
* If no /server/<serverID>/runtime node exists, this is checked
* (it is the global default area for all servers):
* /server/default/runtime/<account_or_group>/<id>
* <p>
* Finally, if no user/process or group nodes are present in the global
* default area, then this is checked:
* /server/default/runtime/default/<id>
* <p>
* No exceptions are thrown, the given initial value will always be safely
* returned.
*
* @param ds
* A bound directory service instance. If this is
* <code>null</code> then the instance will be dynamically
* queried and bound for the duration of the method and then
* unbound at the end. This is a convenience for situations
* in which only a single value is needed. This has negative
* performance implications in the case where multiple values
* are sequentially obtained.
* @param nodeId
* ID of node to obtain. This will be appended to the various
* paths listed above (and in the precedence order specified
* above).
* @param initial
* The default value to return if the directory backed value
* cannot be obtained for ANY reason.
* @param scope
* <code>true</code> if account related search, otherwise server
* related search is assumed.
*
* @return The requested value or the given initial value if for ANY
* reason the directory backed value cannot be obtained.
*/
public static int getDirectoryNodeInt(DirectoryService ds,
String nodeId,
int initial,
boolean scope)
{
return getDirectoryNodeInt(ds, nodeId, initial,
(scope ? DirScope.ACCOUNTS : DirScope.SERVER), null);
}
/**
* Obtain the specified attribute from the directory if it exists or return
* the default value if the directory node doesn't exist with the proper
* type, the directory paths don't exist or the directory is unavailable.
* <p>
* This method implements a search algorithm that allows values to be
* found that are account (user or process) specific or group specific
* within the current server or a global default for all servers.
* <p>
* The implementation iteratively looks up the directory node under:
* /server/<serverID>/runtime/<account_or_group>/<id>
* <p>
* If no user/process or group nodes are present, then this is checked:
* /server/<serverID>/runtime/default/<id>
* <p>
* If no /server/<serverID>/runtime node exists, this is checked
* (it is the global default area for all servers):
* /server/default/runtime/<account_or_group>/<id>
* <p>
* Finally, if no user/process or group nodes are present in the global
* default area, then this is checked:
* /server/default/runtime/default/<id>
* <p>
* No exceptions are thrown, the given initial value will always be safely
* returned.
*
* @param ds
* A bound directory service instance. If this is
* <code>null</code> then the instance will be dynamically
* queried and bound for the duration of the method and then
* unbound at the end. This is a convenience for situations
* in which only a single value is needed. This has negative
* performance implications in the case where multiple values
* are sequentially obtained.
* @param nodeId
* ID of node to obtain. This will be appended to the various
* paths listed above (and in the precedence order specified
* above).
* @param initial
* The default value to return if the directory backed value
* cannot be obtained for ANY reason.
* @param scope
* <code>true</code> if account related search, otherwise server
* related search is assumed.
*
* @return The requested value or the given initial value if for ANY
* reason the directory backed value cannot be obtained.
*/
public static double getDirectoryNodeDouble(DirectoryService ds,
String nodeId,
double initial,
boolean scope)
{
return getDirectoryNodeDouble(ds, nodeId, initial,
(scope ? DirScope.ACCOUNTS : DirScope.SERVER), null);
}
/**
* Obtain the specified attribute from the directory if it exists or return
* the default value if the directory node doesn't exist with the proper
* type, the directory paths don't exist or the directory is unavailable.
* <p>
* This method implements a search algorithm that allows values to be
* found that are account (user or process) specific or group specific
* within the current server or a global default for all servers.
* <p>
* The implementation iteratively looks up the directory node under:
* /server/<serverID>/runtime/<account_or_group>/<id>
* <p>
* If no user/process or group nodes are present, then this is checked:
* /server/<serverID>/runtime/default/<id>
* <p>
* If no /server/<serverID>/runtime node exists, this is checked
* (it is the global default area for all servers):
* /server/default/runtime/<account_or_group>/<id>
* <p>
* Finally, if no user/process or group nodes are present in the global
* default area, then this is checked:
* /server/default/runtime/default/<id>
* <p>
* No exceptions are thrown, the given initial value will always be safely
* returned.
*
* @param ds
* A bound directory service instance. If this is
* <code>null</code> then the instance will be dynamically
* queried and bound for the duration of the method and then
* unbound at the end. This is a convenience for situations
* in which only a single value is needed. This has negative
* performance implications in the case where multiple values
* are sequentially obtained.
* @param nodeId
* ID of node to obtain. This will be appended to the various
* paths listed above (and in the precedence order specified
* above).
* @param initial
* The default value to return if the directory backed value
* cannot be obtained for ANY reason.
* @param scope
* <code>true</code> if account related search, otherwise server
* related search is assumed.
*
* @return The requested value or the given initial value if for ANY
* reason the directory backed value cannot be obtained.
*/
public static char getDirectoryNodeChar(DirectoryService ds,
String nodeId,
char initial,
boolean scope)
{
return getDirectoryNodeChar(ds, nodeId, initial,
(scope ? DirScope.ACCOUNTS : DirScope.SERVER), null);
}
/**
* Obtain the specified attribute from the directory if it exists or return
* the default value if the directory node doesn't exist with the proper
* type, the directory paths don't exist or the directory is unavailable.
* <p>
* This method implements a search algorithm that allows values to be
* found that are account (user or process) specific or group specific
* within the current server or a global default for all servers.
* <p>
* The implementation iteratively looks up the directory node under:
* /server/<serverID>/runtime/<account_or_group>/<id>
* <p>
* If no user/process or group nodes are present, then this is checked:
* /server/<serverID>/runtime/default/<id>
* <p>
* If no /server/<serverID>/runtime node exists, this is checked
* (it is the global default area for all servers):
* /server/default/runtime/<account_or_group>/<id>
* <p>
* Finally, if no user/process or group nodes are present in the global
* default area, then this is checked:
* /server/default/runtime/default/<id>
* <p>
* No exceptions are thrown, the given initial value will always be safely
* returned.
*
* @param ds
* A bound directory service instance. If this is
* <code>null</code> then the instance will be dynamically
* queried and bound for the duration of the method and then
* unbound at the end. This is a convenience for situations
* in which only a single value is needed. This has negative
* performance implications in the case where multiple values
* are sequentially obtained.
* @param nodeId
* ID of node to obtain. This will be appended to the various
* paths listed above (and in the precedence order specified
* above).
* @param initial
* The default value to return if the directory backed value
* cannot be obtained for ANY reason.
* @param scope
* <code>true</code> if account related search, otherwise server
* related search is assumed.
*
* @return The requested value or the given initial value if for ANY
* reason the directory backed value cannot be obtained.
*/
public static String getDirectoryNodeString(DirectoryService ds,
String nodeId,
String initial,
boolean scope)
{
return getDirectoryNodeString(ds, nodeId, initial,
(scope ? DirScope.ACCOUNTS : DirScope.SERVER), null);
}
/**
* Obtain the specified attribute from the directory if it exists or return
* the default value if the directory node doesn't exist with the proper
* type, the directory paths don't exist or the directory is unavailable.
* <p>
* This method implements a search algorithm that allows values to be
* found that are account (user or process) specific or group specific
* within the current server or a global default for all servers.
* <p>
* The implementation iteratively looks up the directory node under:
* /server/<serverID>/runtime/<account_or_group>/<id>
* <p>
* If no user/process or group nodes are present, then this is checked:
* /server/<serverID>/runtime/default/<id>
* <p>
* If no /server/<serverID>/runtime node exists, this is checked
* (it is the global default area for all servers):
* /server/default/runtime/<account_or_group>/<id>
* <p>
* Finally, if no user/process or group nodes are present in the global
* default area, then this is checked:
* /server/default/runtime/default/<id>
* <p>
* No exceptions are thrown, the given initial value will always be safely
* returned.
*
* @param ds
* A bound directory service instance. If this is
* <code>null</code> then the instance will be dynamically
* queried and bound for the duration of the method and then
* unbound at the end. This is a convenience for situations
* in which only a single value is needed. This has negative
* performance implications in the case where multiple values
* are sequentially obtained.
* @param nodeId
* ID of node to obtain. This will be appended to the various
* paths listed above (and in the precedence order specified
* above).
* @param initial
* The default value to return if the directory backed value
* cannot be obtained for ANY reason.
* @param scope
* <code>true</code> if account related search, otherwise server
* related search is assumed.
*
* @return The requested value or the given initial value if for ANY
* reason the directory backed value cannot be obtained.
*/
public static boolean getDirectoryNodeBoolean(DirectoryService ds,
String nodeId,
boolean initial,
boolean scope)
{
return getDirectoryNodeBoolean(ds, nodeId, initial,
(scope ? DirScope.ACCOUNTS : DirScope.SERVER), null);
}
/**
* Obtain the path to the node in the directory if it exists or return
* <code>null</code> if the directory node doesn't exist with the proper
* type, the directory paths don't exist or the directory is unavailable.
* <p>
* This method implements a search algorithm that allows paths to be
* found that are account (user or process) specific or group specific
* within the current server or a global default for all servers.
* <p>
* There are two search scopes: account relayed and server related.
* Account related search is described below. The server related search
* is a version of the search that is limited to the first two server
* related levels.
* <p>
* The implementation iteratively looks up the directory node under:
* /server/<serverID>/runtime/<account_or_group>/<id>
* <p>
* If no user/process or group nodes are present, then this is checked:
* /server/<serverID>/runtime/default/<id>
* <p>
* If no /server/<serverID>/runtime node exists, this is checked
* (it is the global default area for all servers):
* /server/default/runtime/<account_or_group>/<id>
* <p>
* Finally, if no user/process or group nodes are present in the global
* default area, then this is checked:
* /server/default/runtime/default/<id>
*
* @param ds
* A bound directory service instance. If this is
* <code>null</code> then the instance will be dynamically
* queried and bound for the duration of the method and then
* unbound at the end. This is a convenience for situations
* in which only a single value is needed. This has negative
* performance implications in the case where multiple values
* are sequentially obtained.
* @param nodeId
* ID of node to obtain. This will be appended to the various
* paths listed above (and in the precedence order specified
* above).
* @param expected
* Expected class of node. This will be checked with that
* found in the directory for consistency.
* @param scope
* <code>true</code> if account related search, otherwise server
* related search is assumed.
*
* @return The path to the found node or <code>null</code> if for ANY
* reason the directory node cannot be found.
*/
public static String findDirectoryNodePath(DirectoryService ds,
String nodeId,
String expected,
boolean scope)
{
return findDirectoryNodePath(ds, nodeId, expected,
(scope ? DirScope.ACCOUNTS : DirScope.SERVER), null);
}
/**
* Obtain the specified attribute from the directory if it exists or return
* the default value if the directory node doesn't exist with the proper
* type, the directory paths don't exist or the directory is unavailable.
* <p>
* This method implements a search algorithm that allows values to be
* found that are account (user or process) specific or group specific
* within the current server or a global default for all servers.
* <p>
* The implementation iteratively looks up the directory node under:
* /server/<serverID>/runtime/<account_or_group>/<id>
* <p>
* If no user/process or group nodes are present, then this is checked:
* /server/<serverID>/runtime/default/<id>
* <p>
* If no /server/<serverID>/runtime node exists, this is checked
* (it is the global default area for all servers):
* /server/default/runtime/<account_or_group>/<id>
* <p>
* Finally, if no user/process or group nodes are present in the global
* default area, then this is checked:
* /server/default/runtime/default/<id>
* <p>
* No exceptions are thrown, the given initial value will always be safely
* returned.
* <p>
* If the {@code scope} is set to {@link DirScope#BOTH}, first a per-account search will be
* performed. If the value is not found, it fallbacks to a per-server search.
*
* @param ds
* A bound directory service instance. If this is
* <code>null</code> then the instance will be dynamically
* queried and bound for the duration of the method and then
* unbound at the end. This is a convenience for situations
* in which only a single value is needed. This has negative
* performance implications in the case where multiple values
* are sequentially obtained.
* @param nodeId
* ID of node to obtain. This will be appended to the various
* paths listed above (and in the precedence order specified
* above).
* @param initial
* The default value to return if the directory backed value
* cannot be obtained for ANY reason.
* @param scope
* Search mode for the directory node lookup.
* @param accountIds
* Explicit list of account IDs which should be included in the search. When
* {@code null}, the current context's account IDs are resolved and used.
*
* @return The requested value or the given initial value if for ANY
* reason the directory backed value cannot be obtained.
*/
public static int getDirectoryNodeInt(DirectoryService ds,
String nodeId,
int initial,
DirScope scope,
String[] accountIds)
{
DirectoryHelper helper = new DirectoryHelper("value")
{
@Override
public Object getDirectoryNode(DirectoryService ds, String nodeId)
{
return ds.getNodeInteger(nodeId, attrName);
}
};
Integer result = (Integer) getDirectoryNodeWorker(ds,
nodeId,
"integer",
helper,
scope,
accountIds);
return (result == null) ? initial : result.intValue();
}
/**
* Obtain the specified attribute from the directory if it exists or return
* the default value if the directory node doesn't exist with the proper
* type, the directory paths don't exist or the directory is unavailable.
* <p>
* This method implements a search algorithm that allows values to be
* found that are account (user or process) specific or group specific
* within the current server or a global default for all servers.
* <p>
* The implementation iteratively looks up the directory node under:
* /server/<serverID>/runtime/<account_or_group>/<id>
* <p>
* If no user/process or group nodes are present, then this is checked:
* /server/<serverID>/runtime/default/<id>
* <p>
* If no /server/<serverID>/runtime node exists, this is checked
* (it is the global default area for all servers):
* /server/default/runtime/<account_or_group>/<id>
* <p>
* Finally, if no user/process or group nodes are present in the global
* default area, then this is checked:
* /server/default/runtime/default/<id>
* <p>
* No exceptions are thrown, the given initial value will always be safely
* returned.
* <p>
* If the {@code scope} is set to {@link DirScope#BOTH}, first a per-account search will be
* performed. If the value is not found, it fallbacks to a per-server search.
*
* @param ds
* A bound directory service instance. If this is
* <code>null</code> then the instance will be dynamically
* queried and bound for the duration of the method and then
* unbound at the end. This is a convenience for situations
* in which only a single value is needed. This has negative
* performance implications in the case where multiple values
* are sequentially obtained.
* @param nodeId
* ID of node to obtain. This will be appended to the various
* paths listed above (and in the precedence order specified
* above).
* @param initial
* The default value to return if the directory backed value
* cannot be obtained for ANY reason.
* @param scope
* Search mode for the directory node lookup.
* @param accountIds
* Explicit list of account IDs which should be included in the search. When
* {@code null}, the current context's account IDs are resolved and used.
*
* @return The requested value or the given initial value if for ANY
* reason the directory backed value cannot be obtained.
*/
public static Integer getDirectoryNodeInt(DirectoryService ds,
String nodeId,
Integer initial,
DirScope scope,
String[] accountIds)
{
DirectoryHelper helper = new DirectoryHelper("value")
{
@Override
public Object getDirectoryNode(DirectoryService ds, String nodeId)
{
return ds.getNodeInteger(nodeId, attrName);
}
};
Integer result = (Integer) getDirectoryNodeWorker(ds,
nodeId,
"integer",
helper,
scope,
accountIds);
return (result == null) ? initial : result;
}
/**
* Obtain the specified attribute from the directory if it exists or return
* the default value if the directory node doesn't exist with the proper
* type, the directory paths don't exist or the directory is unavailable.
* <p>
* This method implements a search algorithm that allows values to be
* found that are account (user or process) specific or group specific
* within the current server or a global default for all servers.
* <p>
* The implementation iteratively looks up the directory node under:
* /server/<serverID>/runtime/<account_or_group>/<id>
* <p>
* If no user/process or group nodes are present, then this is checked:
* /server/<serverID>/runtime/default/<id>
* <p>
* If no /server/<serverID>/runtime node exists, this is checked
* (it is the global default area for all servers):
* /server/default/runtime/<account_or_group>/<id>
* <p>
* Finally, if no user/process or group nodes are present in the global
* default area, then this is checked:
* /server/default/runtime/default/<id>
* <p>
* No exceptions are thrown, the given initial value will always be safely
* returned.
* <p>
* If the {@code scope} is set to {@link DirScope#BOTH}, first a per-account search will be
* performed. If the value is not found, it fallbacks to a per-server search.
*
* @param ds
* A bound directory service instance. If this is
* <code>null</code> then the instance will be dynamically
* queried and bound for the duration of the method and then
* unbound at the end. This is a convenience for situations
* in which only a single value is needed. This has negative
* performance implications in the case where multiple values
* are sequentially obtained.
* @param nodeId
* ID of node to obtain. This will be appended to the various
* paths listed above (and in the precedence order specified
* above).
* @param initial
* The default value to return if the directory backed value
* cannot be obtained for ANY reason.
* @param scope
* Search mode for the directory node lookup.
* @param accountIds
* Explicit list of account IDs which should be included in the search. When
* {@code null}, the current context's account IDs are resolved and used.
*
* @return The requested value or the given initial value if for ANY
* reason the directory backed value cannot be obtained.
*/
public static double getDirectoryNodeDouble(DirectoryService ds,
String nodeId,
double initial,
DirScope scope,
String[] accountIds)
{
DirectoryHelper helper = new DirectoryHelper("value")
{
@Override
public Object getDirectoryNode(DirectoryService ds, String nodeId)
{
return ds.getNodeDouble(nodeId, attrName);
}
};
Double result = (Double) getDirectoryNodeWorker(ds,
nodeId,
"double",
helper,
scope,
accountIds);
return (result == null) ? initial : result.doubleValue();
}
/**
* Obtain the specified attribute from the directory if it exists or return
* the default value if the directory node doesn't exist with the proper
* type, the directory paths don't exist or the directory is unavailable.
* <p>
* This method implements a search algorithm that allows values to be
* found that are account (user or process) specific or group specific
* within the current server or a global default for all servers.
* <p>
* The implementation iteratively looks up the directory node under:
* /server/<serverID>/runtime/<account_or_group>/<id>
* <p>
* If no user/process or group nodes are present, then this is checked:
* /server/<serverID>/runtime/default/<id>
* <p>
* If no /server/<serverID>/runtime node exists, this is checked
* (it is the global default area for all servers):
* /server/default/runtime/<account_or_group>/<id>
* <p>
* Finally, if no user/process or group nodes are present in the global
* default area, then this is checked:
* /server/default/runtime/default/<id>
* <p>
* No exceptions are thrown, the given initial value will always be safely
* returned.
* <p>
* If the {@code scope} is set to {@link DirScope#BOTH}, first a per-account search will be
* performed. If the value is not found, it fallbacks to a per-server search.
*
* @param ds
* A bound directory service instance. If this is
* <code>null</code> then the instance will be dynamically
* queried and bound for the duration of the method and then
* unbound at the end. This is a convenience for situations
* in which only a single value is needed. This has negative
* performance implications in the case where multiple values
* are sequentially obtained.
* @param nodeId
* ID of node to obtain. This will be appended to the various
* paths listed above (and in the precedence order specified
* above).
* @param initial
* The default value to return if the directory backed value
* cannot be obtained for ANY reason.
* @param scope
* Search mode for the directory node lookup.
* @param accountIds
* Explicit list of account IDs which should be included in the search. When
* {@code null}, the current context's account IDs are resolved and used.
*
* @return The requested value or the given initial value if for ANY
* reason the directory backed value cannot be obtained.
*/
public static char getDirectoryNodeChar(DirectoryService ds,
String nodeId,
char initial,
DirScope scope,
String[] accountIds)
{
DirectoryHelper helper = new DirectoryHelper("value")
{
@Override
public Object getDirectoryNode(DirectoryService ds, String nodeId)
{
return ds.getNodeString(nodeId, attrName);
}
};
String result = (String) getDirectoryNodeWorker(ds,
nodeId,
"string",
helper,
scope,
accountIds);
return (result != null && result.length() > 0) ? result.charAt(0) : initial;
}
/**
* Fills the provided or new created map with key and values found at the node given by its
* relative path. The search algorithm is defined by the scope value. If it is true, then
* accounts search is executed, otherwise servers search is executed only. The key and value
* pairs are defined by "entry" class following this example:
* <code><br>
* <node class="entry" name="entry1"><br>
* <node-attribute name="key" value="PAPERSIZE"/><br>
* <node-attribute name="value" value="letter"/><br>
* </node><br>
* <node class="entry" name="entry2"><br>
* <node-attribute name="key" value="LANG"/><br>
* <node-attribute name="value" value="en_US"/><br>
* </node><br>
* </code>
*
* @param ds
* The directory service
* @param nodeId
* ID of node to obtain. This will be appended to the various
* paths listed above (and in the precedence order specified
* above).
* @param map
* The provided map that can be null
* @param scope
* Search mode for the directory node lookup.
* @param caseInsensitiveKeys
* The true value indicates that all map keys are case insensitive and mapped to
* their upper cases.
* @param accountIds
* Explicit list of account IDs which should be included in the search. When
* {@code null}, the current context's account IDs are resolved and used.
*
* @return The filled provided map or new created map filled with found values.
*/
public static Map<String, String> getDirectoryNodeMap(DirectoryService ds,
String nodeId,
Map<String, String> map,
DirScope scope,
boolean caseInsensitiveKeys,
String[] accountIds)
{
DirectoryHelper helper = new DirectoryHelper(null)
{
@Override
public Object getDirectoryNode(DirectoryService ds, String nodeId)
{
String[] names = ds.enumerateNodes(nodeId);
if (names == null)
{
return map != null ? map : Collections.emptyMap();
}
Map<String, String> nodeMap;
if (map != null)
{
nodeMap = map;
}
else
{
nodeMap = new LinkedHashMap<>();
}
for(String name : names)
{
String entryNodeId = nodeId + "/" + name;
String key = ds.getNodeString(entryNodeId, "key");
String value = ds.getNodeString(entryNodeId, "value");
if (caseInsensitiveKeys)
{
key = key.toUpperCase(Locale.ROOT);
}
nodeMap.put(key, value);
}
return nodeMap;
}
};
Map<String, String> result = (Map<String, String>) getDirectoryNodeWorker(ds,
nodeId,
"container",
helper,
scope,
accountIds);
return (result == null) ? Collections.emptyMap() : result;
}
/**
* Fills the provided or new created map with key and values found at the node given by its
* relative path. The search algorithm is defined by the scope value. If it is true, then
* accounts search is executed, otherwise servers search is executed only. The key and value
* pairs are defined by "entry" class following this example:
* <code><br>
* <node class="entry" name="entry1"><br>
* <node-attribute name="key" value="PAPERSIZE"/><br>
* <node-attribute name="value" value="letter"/><br>
* </node><br>
* <node class="entry" name="entry2"><br>
* <node-attribute name="key" value="LANG"/><br>
* <node-attribute name="value" value="en_US"/><br>
* </node><br>
* </code>
*
* @param ds
* The directory service
* @param nodeRelativePath
* The entries container relative path
* @param map
* The provided map that can be null
* @param scope
* The true value indicates accounts search, otherwise the server search is used only
* @param caseInsensitiveKeys
* The true value indicates that all map keys are case insensitive and mapped to
* their upper cases.
*
* @return The filled provided map or new created map filled with found values.
*/
public static Map<String, String> getDirectoryNodeMap(DirectoryService ds,
String nodeRelativePath,
Map<String, String> map,
boolean scope,
boolean caseInsensitiveKeys)
{
if (!ds.bind())
{
throw new RuntimeException("directory bind failed");
}
try
{
String nodePath = Utils.findDirectoryNodePath(ds,
nodeRelativePath,
"container",
scope);
if (nodePath == null)
{
// not found
return map != null ? map : Collections.emptyMap();
}
String[] names = ds.enumerateNodes(nodePath);
if (names == null)
{
return map != null ? map : Collections.emptyMap();
}
Map<String, String> pathPrefixMap;
if (map != null)
{
pathPrefixMap = map;
}
else
{
pathPrefixMap = new LinkedHashMap<>();
}
for(String name : names)
{
String nodeId = nodePath + "/" + name;
String key = ds.getNodeString(nodeId, "key");
String value = ds.getNodeString(nodeId, "value");
if (caseInsensitiveKeys)
{
key = key.toUpperCase(Locale.ROOT);
}
pathPrefixMap.put(key, value);
}
return pathPrefixMap;
}
finally
{
ds.unbind();
}
}
/**
*
* @param <TBean>
* The parameter type representing the class for a java bean object with all public fields.
* @param ds
* The directory service
* @param nodeRelativePath
* The entries container relative path
* @param list
* The list to fill, it can be null
* @param cls
* The class for objects being retrieved from the directory by this method.
* @param scope
* The true value indicates accounts search, otherwise the server search is used only
* @param attributes
* The true value indicates TBean object properties are serialized as node attributes,
* otherwise properties are given as nodes.
*
* @return The list of target objects.
*
* @throws Throwable
* If the target class has no default constructor or its fields are not accessible or
* the directory service is failed.
*/
public static <TBean> List<TBean> getDirectoryNodeBeanList( DirectoryService ds,
String nodeRelativePath,
List<TBean> list,
Class<TBean> cls,
boolean scope,
boolean attributes)
throws Throwable
{
if (!ds.bind())
{
throw new RuntimeException("directory bind failed");
}
try
{
String nodePath = Utils.findDirectoryNodePath(ds, nodeRelativePath, "container", scope);
if (nodePath == null)
{
// not found
return list != null ? list : Collections.emptyList();
}
String[] names = ds.enumerateNodes(nodePath);
if (names == null)
{
return list != null ? list : Collections.emptyList();
}
List<TBean> resultList;
if (list != null)
{
resultList = list;
}
else
{
resultList = new LinkedList<>();
}
Field[] fields = cls.getFields();
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType mt = MethodType.methodType(void.class);
MethodHandle defCons = lookup.findConstructor(cls, mt);
for (String name : names)
{
String nodeId = nodePath + "/" + name;
Object obj = defCons.invoke();
for (Field f : fields)
{
MethodHandle mh = lookup.unreflectSetter(f);
Object value = null;
String nm = f.getName();
String fieldNodeId, attributeName;
if (attributes)
{
fieldNodeId = nodeId;
attributeName = nm;
}
else
{
fieldNodeId = nodeId + "/" + nm;
attributeName = "value";
}
if (f.getType().equals(String.class))
{
value = ds.getNodeString(fieldNodeId, attributeName);
}
else if (f.getType().equals(Integer.TYPE))
{
value = ds.getNodeInteger(fieldNodeId, attributeName);
}
else if (f.getType().equals(Boolean.TYPE))
{
value = ds.getNodeBoolean(fieldNodeId, attributeName);
}
if (value != null)
{
mh.invoke(obj, value);
}
}
resultList.add((TBean) obj);
}
return resultList;
}
finally
{
ds.unbind();
}
}
/**
* Enumerates the nodes found at the node given by its relative path. The search algorithm is defined by
* the scope value. If it is true, then accounts search is executed, otherwise servers search is
* executed only.
*
* @param directoryService
* The directory service
* @param nodeRelativePath
* The entries container relative path
* @param scope
* The true value indicates accounts search, otherwise the server search is used only
* @param func
* A function called for every sub-node found at the path given by {@code nodeRelativePath}.
* The function is passed node name of every sub-node encountered. The enumeration continues
* until all the sub-nodes are enumerated or the function returns any other value than
* {@code true}.
*
* @return {@code false} when the node given by {@code nodeRelativePath} is not found, {@code true}
* otherwise.
*/
public static boolean enumDirectoryNodes(DirectoryService directoryService,
String nodeRelativePath,
boolean scope,
Function<String, Boolean> func)
{
return doWithBoundDS(directoryService, true, ds ->
{
String nodePath = Utils.findDirectoryNodePath(ds,
nodeRelativePath,
"container",
scope);
if (nodePath == null)
{
// not found
return false;
}
String[] names = ds.enumerateNodes(nodePath);
if (names == null)
{
return false;
}
for (String name : names)
{
Boolean res = func.apply(name);
if (!Objects.equals(res, Boolean.TRUE))
{
break;
}
}
return true;
});
}
/**
* Obtain the specified attribute from the directory if it exists or return
* the default value if the directory node doesn't exist with the proper
* type, the directory paths don't exist or the directory is unavailable.
* <p>
* This method implements a search algorithm that allows values to be
* found that are account (user or process) specific or group specific
* within the current server or a global default for all servers.
* <p>
* The implementation iteratively looks up the directory node under:
* /server/<serverID>/runtime/<account_or_group>/<id>
* <p>
* If no user/process or group nodes are present, then this is checked:
* /server/<serverID>/runtime/default/<id>
* <p>
* If no /server/<serverID>/runtime node exists, this is checked
* (it is the global default area for all servers):
* /server/default/runtime/<account_or_group>/<id>
* <p>
* Finally, if no user/process or group nodes are present in the global
* default area, then this is checked:
* /server/default/runtime/default/<id>
* <p>
* No exceptions are thrown, the given initial value will always be safely
* returned.
* <p>
* If the {@code scope} is set to {@link DirScope#BOTH}, first a per-account search will be
* performed. If the value is not found, it fallbacks to a per-server search.
*
* @param ds
* A bound directory service instance. If this is
* <code>null</code> then the instance will be dynamically
* queried and bound for the duration of the method and then
* unbound at the end. This is a convenience for situations
* in which only a single value is needed. This has negative
* performance implications in the case where multiple values
* are sequentially obtained.
* @param nodeId
* ID of node to obtain. This will be appended to the various
* paths listed above (and in the precedence order specified
* above).
* @param initial
* The default value to return if the directory backed value
* cannot be obtained for ANY reason.
* @param scope
* Search mode for the directory node lookup.
* @param accountIds
* Explicit list of account IDs which should be included in the search. When
* {@code null}, the current context's account IDs are resolved and used.
*
* @return The requested value or the given initial value if for ANY
* reason the directory backed value cannot be obtained.
*/
public static String getDirectoryNodeString(DirectoryService ds,
String nodeId,
String initial,
DirScope scope,
String[] accountIds)
{
DirectoryHelper helper = new DirectoryHelper("value")
{
@Override
public Object getDirectoryNode(DirectoryService ds, String nodeId)
{
return ds.getNodeString(nodeId, attrName);
}
};
String result = (String) getDirectoryNodeWorker(ds,
nodeId,
"string",
helper,
scope,
accountIds);
return (result == null) ? initial : result;
}
/**
* Obtain the specified attribute values from the directory if it exists or return
* an empty list if the directory node doesn't exist with the proper
* type, the directory paths don't exist or the directory is unavailable.
* <p>
* This method implements a search algorithm that allows values to be
* found that are account (user or process) specific or group specific
* within the current server or a global default for all servers.
* <p>
* The implementation iteratively looks up the directory node under:
* /server/<serverID>/runtime/<account_or_group>/<id>
* <p>
* If no user/process or group nodes are present, then this is checked:
* /server/<serverID>/runtime/default/<id>
* <p>
* If no /server/<serverID>/runtime node exists, this is checked
* (it is the global default area for all servers):
* /server/default/runtime/<account_or_group>/<id>
* <p>
* Finally, if no user/process or group nodes are present in the global
* default area, then this is checked:
* /server/default/runtime/default/<id>
* <p>
* No exceptions are thrown, the given initial value will always be safely
* returned.
* <p>
* If the {@code scope} is set to {@link DirScope#BOTH}, first a per-account search will be
* performed. If the value is not found, it fallbacks to a per-server search.
*
* @param ds
* A bound directory service instance. If this is
* {@code null} then the instance will be dynamically
* queried and bound for the duration of the method and then
* unbound at the end. This is a convenience for situations
* in which only a single value is needed. This has negative
* performance implications in the case where multiple values
* are sequentially obtained.
* @param nodeId
* ID of node to obtain. This will be appended to the various
* paths listed above (and in the precedence order specified
* above).
* @param scope
* Search mode for the directory node lookup.
* @param accountIds
* Explicit list of account IDs which should be included in the search. When
* {@code null}, the current context's account IDs are resolved and used.
*
* @return The requested value or the given initial value if for ANY
* reason the directory backed value cannot be obtained.
*/
public static List<String> getDirectoryNodeStrings(DirectoryService ds,
String nodeId,
DirScope scope,
String[] accountIds)
{
DirectoryHelper helper = new DirectoryHelper(null)
{
@Override
public Object getDirectoryNode(DirectoryService ds, String nodeId)
{
return ds.getNodeStrings(nodeId, "values");
}
};
String[] result = (String[]) getDirectoryNodeWorker(ds,
nodeId,
"strings",
helper,
scope,
accountIds);
return (result == null) ? Collections.emptyList() : Arrays.asList(result);
}
/**
* Obtain the specified attribute from the directory if it exists or return
* the default value if the directory node doesn't exist with the proper
* type, the directory paths don't exist or the directory is unavailable.
* <p>
* This method implements a search algorithm that allows values to be
* found that are account (user or process) specific or group specific
* within the current server or a global default for all servers.
* <p>
* The implementation iteratively looks up the directory node under:
* /server/<serverID>/runtime/<account_or_group>/<id>
* <p>
* If no user/process or group nodes are present, then this is checked:
* /server/<serverID>/runtime/default/<id>
* <p>
* If no /server/<serverID>/runtime node exists, this is checked
* (it is the global default area for all servers):
* /server/default/runtime/<account_or_group>/<id>
* <p>
* Finally, if no user/process or group nodes are present in the global
* default area, then this is checked:
* /server/default/runtime/default/<id>
* <p>
* No exceptions are thrown, the given initial value will always be safely
* returned.
* <p>
* If the {@code scope} is set to {@link DirScope#BOTH}, first a per-account search will be
* performed. If the value is not found, it fallbacks to a per-server search.
*
* @param ds
* A bound directory service instance. If this is
* <code>null</code> then the instance will be dynamically
* queried and bound for the duration of the method and then
* unbound at the end. This is a convenience for situations
* in which only a single value is needed. This has negative
* performance implications in the case where multiple values
* are sequentially obtained.
* @param nodeId
* ID of node to obtain. This will be appended to the various
* paths listed above (and in the precedence order specified
* above).
* @param initial
* The default value to return if the directory backed value
* cannot be obtained for ANY reason.
* @param scope
* Search mode for the directory node lookup.
* @param accountIds
* Explicit list of account IDs which should be included in the search. When
* {@code null}, the current context's account IDs are resolved and used.
*
* @return The requested value or the given initial value if for ANY
* reason the directory backed value cannot be obtained.
*/
public static boolean getDirectoryNodeBoolean(DirectoryService ds,
String nodeId,
boolean initial,
DirScope scope,
String[] accountIds)
{
DirectoryHelper helper = new DirectoryHelper("value")
{
@Override
public Object getDirectoryNode(DirectoryService ds, String nodeId)
{
return ds.getNodeBoolean(nodeId, attrName);
}
};
Boolean result = (Boolean) getDirectoryNodeWorker(ds,
nodeId,
"boolean",
helper,
scope,
accountIds);
return (result == null) ? initial : result.booleanValue();
}
/**
* Obtain the specified attribute from the directory if it exists or return
* the default value if the directory node doesn't exist with the proper
* type, the directory paths don't exist or the directory is unavailable.
* <p>
* This method implements a search algorithm that allows values to be
* found that are account (user or process) specific or group specific
* within the current server or a global default for all servers.
* <p>
* The implementation iteratively looks up the directory node under:
* /server/<serverID>/runtime/<account_or_group>/<id>
* <p>
* If no user/process or group nodes are present, then this is checked:
* /server/<serverID>/runtime/default/<id>
* <p>
* If no /server/<serverID>/runtime node exists, this is checked
* (it is the global default area for all servers):
* /server/default/runtime/<account_or_group>/<id>
* <p>
* Finally, if no user/process or group nodes are present in the global
* default area, then this is checked:
* /server/default/runtime/default/<id>
* <p>
* No exceptions are thrown, the given initial value will always be safely
* returned.
* <p>
* If the {@code scope} is set to {@link DirScope#BOTH}, first a per-account search will be
* performed. If the value is not found, it fallbacks to a per-server search.
*
* @param ds
* A bound directory service instance. If this is
* <code>null</code> then the instance will be dynamically
* queried and bound for the duration of the method and then
* unbound at the end. This is a convenience for situations
* in which only a single value is needed. This has negative
* performance implications in the case where multiple values
* are sequentially obtained.
* @param nodeId
* ID of node to obtain. This will be appended to the various
* paths listed above (and in the precedence order specified
* above).
* @param initial
* The default value to return if the directory backed value
* cannot be obtained for ANY reason.
* @param scope
* Search mode for the directory node lookup.
* @param accountIds
* Explicit list of account IDs which should be included in the search. When
* {@code null}, the current context's account IDs are resolved and used.
*
* @return The requested value or the given initial value if for ANY
* reason the directory backed value cannot be obtained.
*/
public static Boolean getDirectoryNodeBoolean(DirectoryService ds,
String nodeId,
Boolean initial,
DirScope scope,
String[] accountIds)
{
DirectoryHelper helper = new DirectoryHelper("value")
{
@Override
public Object getDirectoryNode(DirectoryService ds, String nodeId)
{
return ds.getNodeBoolean(nodeId, attrName);
}
};
Boolean result = (Boolean) getDirectoryNodeWorker(ds,
nodeId,
"boolean",
helper,
scope,
accountIds);
return (result == null) ? initial : result;
}
/**
* Obtain the specified attribute from the directory if it exists or return
* the default value if the directory node doesn't exist with the proper
* type, the directory paths don't exist or the directory is unavailable.
* <p>
* This method implements a search algorithm that allows values to be
* found that are account (user or process) specific or group specific
* within the current server or a global default for all servers.
* <p>
* The implementation iteratively looks up the directory node under:
* /server/<serverID>/runtime/<account_or_group>/<id>
* <p>
* If no user/process or group nodes are present, then this is checked:
* /server/<serverID>/runtime/default/<id>
* <p>
* If no /server/<serverID>/runtime node exists, this is checked
* (it is the global default area for all servers):
* /server/default/runtime/<account_or_group>/<id>
* <p>
* Finally, if no user/process or group nodes are present in the global
* default area, then this is checked:
* /server/default/runtime/default/<id>
* <p>
* No exceptions are thrown, the given initial value will always be safely
* returned.
* <p>
* If the {@code scope} is set to {@link DirScope#BOTH}, first a per-account search will be
* performed. If the value is not found, it fallbacks to a per-server search.
*
* @param ds
* A bound directory service instance. If this is
* <code>null</code> then the instance will be dynamically
* queried and bound for the duration of the method and then
* unbound at the end. This is a convenience for situations
* in which only a single value is needed. This has negative
* performance implications in the case where multiple values
* are sequentially obtained.
* @param nodeId
* ID of node to obtain. This will be appended to the various
* paths listed above (and in the precedence order specified
* above).
* @param initial
* The default value to return if the directory backed value
* cannot be obtained for ANY reason.
* @param scope
* Search mode for the directory node lookup.
* @param accountIds
* Explicit list of account IDs which should be included in the search. When
* {@code null}, the current context's account IDs are resolved and used.
*
* @return The requested value or the given initial value if for ANY
* reason the directory backed value cannot be obtained.
*/
public static byte[] getDirectoryNodeByteArray(DirectoryService ds,
String nodeId,
byte[] initial,
DirScope scope,
String[] accountIds)
{
DirectoryHelper helper = new DirectoryHelper("value")
{
@Override
public Object getDirectoryNode(DirectoryService ds, String nodeId)
{
return ds.getNodeByteArray(nodeId, attrName);
}
};
byte[] result = (byte[]) getDirectoryNodeWorker(ds,
nodeId,
"bytes",
helper,
scope,
accountIds);
return (result == null) ? initial : result;
}
/**
* Enumerate nodes in the specified container from the directory if it exists or return
* an empty list if the container directory node doesn't exist with the proper
* type, the directory paths don't exist or the directory is unavailable.
* <p>
* This method implements a search algorithm that allows values to be
* found that are account (user or process) specific or group specific
* within the current server or a global default for all servers.
* <p>
* The implementation iteratively looks up the directory node under:
* /server/<serverID>/runtime/<account_or_group>/<id>
* <p>
* If no user/process or group nodes are present, then this is checked:
* /server/<serverID>/runtime/default/<id>
* <p>
* If no /server/<serverID>/runtime node exists, this is checked
* (it is the global default area for all servers):
* /server/default/runtime/<account_or_group>/<id>
* <p>
* Finally, if no user/process or group nodes are present in the global
* default area, then this is checked:
* /server/default/runtime/default/<id>
* <p>
* No exceptions are thrown, a valid list will always be safely returned.
* <p>
* If the {@code scope} is set to {@link DirScope#BOTH}, first a per-account search will be
* performed. If the value is not found, it fallbacks to a per-server search.
*
* @param ds
* A bound directory service instance. If this is {@code null} then the instance
* will be dynamically queried and bound for the duration of the method and then
* unbound at the end. This is a convenience for situations in which only a single
* value is needed. This has negative performance implications in the case where
* multiple values are sequentially obtained.
* @param nodeId
* ID of node to obtain. This will be appended to the various paths listed above
* (and in the precedence order specified above).
* @param scope
* Search mode for the directory node lookup.
* @param accountIds
* Explicit list of account IDs which should be included in the search. When
* {@code null}, the current context's account IDs are resolved and used.
*
* @return List of directory node ids or the an empty list if for ANY
* reason the directory backed value cannot be obtained.
*/
public static List<String> enumerateNodes(DirectoryService ds,
String nodeId,
DirScope scope,
String[] accountIds)
{
DirectoryHelper helper = new DirectoryHelper(null)
{
@Override
public Object getDirectoryNode(DirectoryService ds, String nodeId)
{
return ds.enumerateNodes(nodeId);
}
};
String[] result = (String[]) getDirectoryNodeWorker(ds,
nodeId,
"container",
helper,
scope,
accountIds);
return (result == null) ? Collections.emptyList() : Arrays.asList(result);
}
/**
* Obtain the path to the node in the directory if it exists or return
* <code>null</code> if the directory node doesn't exist with the proper
* type, the directory paths don't exist or the directory is unavailable.
* <p>
* This method implements a search algorithm that allows paths to be
* found that are account (user or process) specific or group specific
* within the current server or a global default for all servers.
* <p>
* There are two search scopes: account relayed and server related.
* Account related search is described below. The server related search
* is a version of the search that is limited to the first two server
* related levels.
* <p>
* The implementation iteratively looks up the directory node under:
* /server/<serverID>/runtime/<account_or_group>/<id>
* <p>
* If no user/process or group nodes are present, then this is checked:
* /server/<serverID>/runtime/default/<id>
* <p>
* If no /server/<serverID>/runtime node exists, this is checked
* (it is the global default area for all servers):
* /server/default/runtime/<account_or_group>/<id>
* <p>
* Finally, if no user/process or group nodes are present in the global
* default area, then this is checked:
* /server/default/runtime/default/<id>
* <p>
* If the {@code scope} is set to {@link DirScope#BOTH}, first a per-account search will be
* performed. If the value is not found, it fallbacks to a per-server search.
*
* @param ds
* A bound directory service instance. If this is
* <code>null</code> then the instance will be dynamically
* queried and bound for the duration of the method and then
* unbound at the end. This is a convenience for situations
* in which only a single value is needed. This has negative
* performance implications in the case where multiple values
* are sequentially obtained.
* @param nodeId
* ID of node to obtain. This will be appended to the various
* paths listed above (and in the precedence order specified
* above).
* @param expected
* Expected class of node. This will be checked with that
* found in the directory for consistency.
* @param scope
* Search mode for the directory node lookup.
* @param accountIds
* Explicit list of account IDs which should be included in the search. When
* {@code null}, the current context's account IDs are resolved and used.
*
* @return The path to the found node or <code>null</code> if for ANY
* reason the directory node cannot be found.
*/
public static String findDirectoryNodePath(DirectoryService ds,
String nodeId,
String expected,
DirScope scope,
String[] accountIds)
{
DirectoryHelper helper = new DirectoryHelper("")
{
/**
* @param ds
*/
@Override
public Object getDirectoryNode(DirectoryService ds, String nodeId)
{
// simply returns the path when called, this works because the
// calling code has already checked the path for existence and
// type matches
return nodeId;
}
};
return (String) getDirectoryNodeWorker(ds, nodeId, expected, helper,
scope, accountIds);
}
/**
* Obtain the specified attribute from the directory if it exists or return the {@code null} if
* the directory node doesn't exist with the proper type, the directory paths don't exist or
* the directory is unavailable.
* <p>
* This method implements a search algorithm that allows values to be found that are account
* (user or process) specific or group specific within the current server or a global default
* for all servers.
* <p>
* The implementation iteratively looks up the directory node under:
* <ol>
* <li>if a {@code projectToken} is configured (same stands for next all pairs below):<br>
* {@code /server/<serverID>/runtime/<account_or_group>/<id>.<project>};</li>
* <li>{@code /server/<serverID>/runtime/<account_or_group>/<id>};</li>
* <li>if no user/process or group nodes are present, then these checked:<br>
* {@code /server/<serverID>/runtime/default/<id>.<project>;}</li>
* <li>{@code /server/<serverID>/runtime/default/<id>;}</li>
* <li>if no {@code /server/<serverID>/runtime} node exists, these are checked (it is the
* global default area for all servers):<br>
* {@code /server/default/runtime/<account_or_group>/<id>.<project>;}</li>
* <li>{@code /server/default/runtime/<account_or_group>/<id>}</li>
* <li>finally, if no user/process or group nodes are present in the global default area,
* then these are checked:<br>
* {@code /server/default/runtime/default/<id>.<project>;}</li>
* <li>{@code /server/default/runtime/default/<id>}.</li>
* </ol>
* No exceptions are thrown, the requested attribute of {@code null} will always be safely
* returned.
* If the {@code scope} is set to {@link DirScope#BOTH}, first a per-account search will be
* performed. If the value is not found, it fallbacks to a per-server search.
*
* @param directoryService
* A bound directory service instance. If this is {@code null} then the instance
* will be dynamically queried and bound for the duration of the method and then
* unbound at the end. This is a convenience for situations in which only a single
* value is needed. This has negative performance implications in the case where
* multiple values are sequentially obtained.
* @param nodeId
* ID of node to obtain. This will be appended to the various paths listed above
* (and in the precedence order specified above).
* @param expected
* Expected class of node or {@code null}. If given, this will be checked with that
* found in the directory for consistency.
* @param helper
* The data type-specific helper to query the given node.
* @param scope
* Search mode for the directory node lookup.
* @param accountIds
* Explicit list of account IDs which should be included in the search. When
* {@code null}, the current context's account IDs are resolved and used.
*
* @return The requested value or {@code null} if for ANY reason the directory backed value
* cannot be obtained.
*/
@SuppressWarnings("null")
public static Object getDirectoryNodeWorker(DirectoryService directoryService,
String nodeId,
String expected,
DirectoryHelper helper,
DirScope scope,
final String[] accountIds)
{
try
{
return doWithBoundDS(directoryService, false, ds ->
{
String projSpecNode = null; // compute project specific node
String pToken = work.get().projectToken;
if (pToken != null)
{
projSpecNode = nodeId + "." + pToken;
}
// access the security manager
SecurityManager sm = SecurityManager.getInstance();
// any failure?
if (sm == null && scope == DirScope.ACCOUNTS)
{
// can't access the security manager
return null;
}
// obtain our context-specific values
String serverId = ds.getServerId();
String[] nullAcct = new String[]{null};
String[] accIds = accountIds;
if (accIds == null && (scope == DirScope.BOTH || scope == DirScope.ACCOUNTS))
{
accIds = sm.getAccountIds();
}
// check account availability
if (scope == DirScope.ACCOUNTS && accIds.length == 0)
{
// something is wrong with our context
return null;
}
// our search algorithm's precedence order
// 1. /server/<serverID>/runtime/<account_or_group>/<id>.<project>
// 2. /server/<serverID>/runtime/<account_or_group>/<id>
// 3. /server/<serverID>/runtime/default/<id>.<project>
// 4. /server/<serverID>/runtime/default/<id>
// 5. /server/default/runtime/<account_or_group>/<id>.<project>
// 6. /server/default/runtime/<account_or_group>/<id>
// 7. /server/default/runtime/default/<id>.<project>
// 8. /server/default/runtime/default/<id>
// or
// 1. /server/<serverID>/<id>.<project>
// 2. /server/<serverID>/<id>
// 3. /server/default/<id>.<project>
// 4. /server/default/<id>
// if search scope is "BOTH", the precedence order is per-account then per-server.
String cls = (expected == null) ? null : "/meta/class/" + expected;
String pathSrv = "/server/" + serverId;
// check the specific server first and then the global default
String[] rootPaths = null;
if (scope == DirScope.ACCOUNTS)
{
rootPaths = new String[]
{
pathSrv + "/runtime",
"/server/default/runtime"
};
}
else if (scope == DirScope.SERVER)
{
rootPaths = new String[]
{
pathSrv,
"/server/default"
};
}
else
{
// use a fallback approach: first by account and last by server
rootPaths = new String[]
{
pathSrv + "/runtime",
"/server/default/runtime",
pathSrv,
"/server/default"
};
}
Object result = null;
// loop through the target paths
outer:
for (int i = 0; i < rootPaths.length; i++)
{
boolean onSrvPath = (scope == DirScope.SERVER || (scope == DirScope.BOTH && i >= 2));
// when searching per-server, use the "null" as account
// when searching per-account, use the real accounts
accIds = (onSrvPath ? nullAcct : accIds);
// check each account
for (int j = 0; j < accIds.length; j++)
{
if (projSpecNode != null)
{
result = executeLookup(ds, rootPaths[i], accIds[j], projSpecNode, cls, helper);
if (result != null)
{
break outer;
}
}
result = executeLookup(ds, rootPaths[i], accIds[j], nodeId, cls, helper);
if (result != null)
{
break outer;
}
}
if (onSrvPath)
{
continue;
}
if (projSpecNode != null)
{
result = executeLookup(ds, rootPaths[i], "default", projSpecNode, cls, helper);
if (result != null)
{
break outer;
}
}
// check for a default for all accounts
result = executeLookup(ds, rootPaths[i], "default", nodeId, cls, helper);
if (result != null)
{
break outer;
}
}
return result;
});
}
catch (Exception exc)
{
// just allow null to be returned
LOG.log(Level.WARNING, "An exception during query of Directory service.", exc);
}
return null;
}
/**
* The method executes the supplied {@code code} with a bound Directory service.
*
* @param ds
* If {@code null}, the method will query the service itself and bind it. If an instance is
* supplied, the method will bind it unless already bound.
* @param _throw
* otherwise the method returns {@code null}.
* When {@code true} RuntimeException is thrown when directory service can't be resolved,
* @param code
* The function to execute. The bound Directory service is passed in.
*
* @return the value returned from {@code code}.
*/
public static <T> T doWithBoundDS(DirectoryService ds, boolean _throw, Function<DirectoryService, T> code)
{
if (ds == null)
{
// access the directory service
ds = DirectoryService.getInstance();
// is the directory service is unavailable?
if (ds == null)
{
// done!
if (_throw)
{
throw new RuntimeException("Directory service is unavailable!");
}
else
{
return null;
}
}
}
boolean needsBind = !ds.isBound();
// try to bind
if (needsBind && !ds.bind())
{
// can't access the directory
if (_throw)
{
throw new RuntimeException("Can not bind Directory service!");
}
else
{
return null;
}
}
try
{
return code.apply(ds);
}
finally
{
// if we are one-shot, cleanup
if (needsBind)
{
// last use of the directory, end our session with it
ds.unbind();
}
}
}
/**
* Gets the path resolver for the current directory settings.
*
* @param fileSeparator
* The given legacy file separator
* @param caseSensitive
* The given case sensitive mode
* @param pathMap
* The given map of the legacy file system to the host file system
*
* @return The path resolver
*/
public static PathResolver getPathResolver(String fileSeparator,
boolean caseSensitive,
Map<String, String> pathMap)
{
PathResolverImpl resolver = new PathResolverImpl(fileSeparator, !caseSensitive);
resolver.setPathMap(pathMap);
return resolver;
}
/**
* Gets the path resolver that silently logs all cases when UnResolvedPathException is thrown.
*
* @param fileSeparator
* The given legacy file separator
* @param caseSensitive
* The given case sensitive mode
* @param pathMap
* The given map of the legacy file system to the host file system
*
* @return The safe path resolver
*/
public static PathResolverContainer getSafePathResolver(String fileSeparator,
boolean caseSensitive,
Map<String, String> pathMap)
{
PathResolver resolver = getPathResolver(fileSeparator, caseSensitive, pathMap);
return new PathResolverContainer(resolver);
}
/**
* Tries to set the temporary directory
*
* @param temporaryDirectory
* is the path of the temporary directory
*/
public static void setTemporaryDirectory(String temporaryDirectory)
{
if (temporaryDirectory == null) return;
Path path = Paths.get(temporaryDirectory);
if (!Files.isDirectory(path) || !Files.isWritable(path))
{
String text = "Unable to open or create " + temporaryDirectory + ", error 2";
PostInitErrorManager.addError(() -> ErrorManager.recordOrThrowError(354, text, false));
};
WorkArea wa = work.get();
wa.temporaryDirectory = temporaryDirectory + File.separator + wa.defaultFwdTempFolder;
}
/**
* Returns the explicitly set temporary directory
*
* @return the temporary directory
*/
public static String getTemporaryDirectory()
{
return work.get().temporaryDirectory;
}
/**
* Get or create the default directory for temporary files used by FWD.
*
* @return The default directory for temporary files
*/
public static String getOrCreateDefaultFwdTemporaryDirectory()
{
WorkArea wa = work.get();
return wa.temporaryDirectory == null ?
getOrCreateTemporaryDirectory(wa.defaultFwdTempFolder) :
wa.temporaryDirectory;
}
/**
* Get or create a directory for temporary files used by FWD.
*
* @param folderName
* The name of the folder to be created in the temporary directory.
*
* @return The directory for temporary files
*/
public static String getOrCreateTemporaryDirectory(String folderName)
{
// setting up temporary files upload directory
String tmpDir = getTmpDir(folderName);
File tmpDirObj = new File(tmpDir);
if (!tmpDirObj.exists())
{
tmpDirObj.mkdir();
// add a JVM hook to delete this folder (and all its content).
OrderedShutdownHooks.registerShutdownHook(1, new Thread(() ->
{
int res = FileSystemDaemon.delete(tmpDirObj, true);
if (res != FileSystem.ERR_NO_ERROR)
{
String path = tmpDirObj.getAbsolutePath();
LOG.log(Level.SEVERE, "Error " + res + " while removing the FWD temporary folder: " + path);
}
}));
}
// adding file separator to the end
tmpDir = tmpDir.concat(File.separator);
return tmpDir;
}
/**
* This method returns the full path of the temporary folder.
*
* @param folderName
* The randomly generated temporary sub-folder name.
*
* @return The full path of the temporary folder.
*/
private static String getTmpDir(String folderName)
{
String tmpDir = System.getProperty("java.io.tmpdir");
if (!Files.isWritable(Paths.get(tmpDir)))
{ // for the case the regular way above does not work
tmpDir = System.getProperty("user.home");
}
return tmpDir + File.separator + folderName;
}
/**
* Removes the specified directory node, including all child nodes.
*
* @param ds
* A bound directory service instance with an editing batch open
* @param nodeId
* ID of node to remove
*
* @return <code>true</code> if removed the node successfully
*/
public static boolean removeDirectoryNode(DirectoryService ds, String nodeId)
{
// enumerate and remove all children first, recursively
String[] child = ds.enumerateNodes(nodeId);
if (child == null)
{
return false;
}
for (int i = 0; i < child.length; i++)
{
if (!removeDirectoryNode(ds, nodeId + "/" + child[i]))
{
return false;
}
}
// remove the specified node now
return ds.deleteNode(nodeId);
}
/**
* Extract extension from the file name
*
* @param fileName
* file name
*
* @return extension (portion after the last dot)
*/
public static String getExt(String fileName)
{
if (fileName == null)
{
return null;
}
String fn = new File(fileName).getName();
int extPos = fn.lastIndexOf('.');
return extPos < 0 ? "" : fn.substring(extPos + 1).trim();
}
/**
* Get mimeType of the mouse cursor file
*
* @param fileName
* file name
*
* @return mimeType
*/
public static String mimeType(String fileName)
{
return ImageExt.fromExt(getExt(fileName)).mimeType;
}
/**
* Obtain the port number which has been mapped in the directory to a given
* service name. This is a partial replacement for the service normally
* provided by Unix' <code>getservbyname()</code> API.
* <p>
* It is <i>partial</i> because it only will return a valid port number for
* services explicitly mapped in the directory. The current implementation
* knows nothing of the mappings provided in the standard services file.
* Thus, it should be used only for custom services mapped to ports in the
* directory.
* <p>
* The following directory paths are checked for mappings in the following
* precedence order:
* <p>
* <code>/server/<server_id>/port_services/<service></code><br>
* <code>/server/default/port_services/<service></code>
* <p>
* where:
* <ul>
* <li><code><server_id></code> is the unique identifier of the
* current P2J server instance; and</li>
* <li><code><service></code> is the service name given by the
* <code>name</code> parameter to this method.</li>
* </ul>
*
* @param ds
* A bound directory service instance. If this is
* <code>null</code> then the instance will be dynamically
* queried and bound for the duration of the method and then
* unbound at the end. This is a convenience for situations
* in which only a single value is needed. This has negative
* performance implications in the case where multiple values
* are sequentially obtained.
* @param name
* Service name to look up.
*
* @return The requested value or -1 if the given service is not mapped to
* any port number in the directory paths listed above.
*/
public static int getPortForService(DirectoryService ds, String name)
{
DirectoryHelper helper = new DirectoryHelper("value")
{
@Override
public Object getDirectoryNode(DirectoryService ds, String nodeId)
{
return ds.getNodeInteger(nodeId, attrName);
}
};
StringBuilder buf = new StringBuilder("port_services/");
buf.append(name);
Integer result = (Integer) getDirectoryNodeWorker(ds,
buf.toString(),
"integer",
helper,
DirScope.SERVER,
null);
return ((result == null) ? -1 : result.intValue());
}
/**
* Serves as a placeholder for some interactive help.
*/
public static void noHelp()
{
LogicalTerminal.messageBox("No application help is available");
}
/**
* Get the short name of a class or interface (everything after the last
* dot in the fully qualified class name).
*
* @param cls
* Class.
*
* @return Unqualified class name.
*/
public static String getUnqualifiedName(Class<?> cls)
{
String name = cls.getName();
return name.substring(name.lastIndexOf(".") + 1);
}
/**
* Apply function to the list elements while the result is <code>null</code>
*
* @param list
* the list to be traversed
* @param fn
* Function to be applied
*
* @return the first non-null result or <code>null</code> if none found
*
*/
public static <T,S> T tryWhileNull(List<S> list, Function<S,T> fn)
{
return list.stream().map(fn).
filter(r -> r != null).findFirst().orElse(null);
}
/**
* Find as many files as case-insensitively match the file specified by
* <code>fullPath</code>, where the directory specified by
* <code>leading</code> represents a leading portion of the given path.
* <p>
* This method calls itself recursively with progressively more specific
* <code>leading</code> directories, until <code>leading</code> represents
* the full path qualifier for the file which is the target of the match.
* <p>
* All paths in this method are processed using the current system's file
* separator character. This means that any input to this method must
* have already been properly converted to the system file separator.
*
* @param leading
* A leading portion of the directory qualifier for the
* <code>fullPath</code> argument. The top-level call to this
* method would pass <code>null</code> here. Recursive calls will
* pass a value. If given, this MUST be a valid directory in the
* file system AND it must be a valid prefix for the target file
* (it must case-insensitively match the begining of the target
* filename).
* @param fullPath
* A canonicalized path name which represents an absolute path of
* the file to be matched. Must NOT be <code>null</code>.
* @param list
* A list in which to store matching <code>File</code> objects
* (which is the result of this processing). Must NOT be
* <code>null</code>.
*/
private static void matchPathCaseInsensitively(File leading,
String fullPath,
List<File> list)
{
final String SEP = File.separator;
int pos = 0;
// process leading portion (pull out the directory name and calculate
// its length when ending with the file separator character)
if (leading != null)
{
String dirName = leading.getAbsolutePath();
pos = dirName.length();
// safety check
if (!dirName.equalsIgnoreCase(fullPath.substring(0, pos)))
{
// TODO: add error reporting/logging
// leading portion cannot match the target file name
return;
}
if (!dirName.endsWith(SEP))
{
pos += SEP.length(); // always 1, from File.separator javadoc
}
}
// gather everything after the leading portion
String remaining = fullPath.substring(pos);
// find the end of the next path segment; this is the last segment (which
// is the base file name itself) if this is -1
pos = remaining.indexOf(SEP);
// extract the portion of the path which corresponds with the directory
// level we're currently scanning; this is a path segment only
final String match = (pos >= 0
? remaining.substring(0, pos)
: remaining);
// file filter which matches case-insensitively on the match string
final FileFilter ff = new FileFilter()
{
@Override
public boolean accept(File test)
{
// this only checks one segment at a time, not portions that
// "cross" or include file separator characters
String name = test.getName();
if (name.isEmpty())
{
// special care on windows: when comparing the drive (C:\) segments the name
// is empty because there is no directory.
// manually comparing the drives:
return test.getAbsolutePath().equalsIgnoreCase(match + SEP);
}
return name.equalsIgnoreCase(match);
}
};
File[] files = null;
if (leading == null)
{
// top-level call to this method
// special case: no leading directory specified, so we start at the
// file system roots and try to match the portion of our path up to
// the first file separator
File[] roots = File.listRoots();
List<File> l = new ArrayList<>(roots.length);
for (File next : roots)
{
if (ff.accept(next))
{
l.add(next);
}
}
files = l.toArray(new File[l.size()]);
}
else
{
// recursive call to this method
// safety code; leading must always be a directory
if (!leading.isDirectory())
{
// TODO: add error reporting/logging
// this is definitely not a match
return;
}
files = leading.listFiles(ff);
if (files == null)
{
// TODO: add error reporting/logging
// this should only occur on an I/O error
return;
}
}
if (pos < 0)
{
// last level of search; the contents of the files array contains our final result(s)
Collections.addAll(list, files);
}
else
{
// search recursively down through next set of matching directories
for (File next : files)
{
// file matches cannot be checked further using recursion and they
// can't match anyway, so avoid them
if (next.isDirectory())
{
matchPathCaseInsensitively(next, fullPath, list);
}
}
}
}
/**
* Builds the node path from its parts, checks that it exists with the
* correct data type and if so, returns the value. The node path that
* will be searched is:
* <p>
* <pre>
* root + "/" + postfix + "/" + nodeId
* </pre>
*
* @param ds
* A bound directory service instance. Must not be
* <code>null</code>.
* @param root
* First part of the path to build.
* @param postfix
* Second part of the path to build or <code>null</code>.
* @param nodeId
* ID of node to obtain. This will be appended to the two
* paths listed above.
* @param expected
* Expected class of node or <code>null</code>. If given, this
* will be checked with that found in the directory for
* consistency.
* @param helper
* The data type-specific helper to query the given node.
*
* @return The requested value or <code>null</code> if for ANY
* reason the directory backed value cannot be obtained.
*/
private static Object executeLookup(DirectoryService ds,
String root,
String postfix,
String nodeId,
String expected,
DirectoryHelper helper)
{
StringBuilder path = new StringBuilder();
path.append(root).append("/");
if (postfix != null)
{
path.append(postfix).append("/");
}
path.append(nodeId);
String current = path.toString();
String nodeClass = ds.getNodeClass(current);
if (nodeClass != null)
{
if (expected == null || nodeClass.equals(expected))
{
// we found our path!
return helper.getDirectoryNode(ds, current);
}
else
{
if (LOG.isLoggable(Level.WARNING))
{
final String msg = "Node %s is expected to have class %s, but is configured as %s.";
LOG.logp(Level.WARNING, "Utils", "executeLookup",
String.format(msg, current, expected, nodeClass));
}
}
}
// nothing found or data type is incorrect, continue search
return null;
}
/**
* Generic type safe equals. It can be used for <code>null</code> objects
* without fear of NPE. If both provided objects are <code>null</code> then
* they are assumed equal.
* @param <T>
* Type parameter.
* @param obj1
* First object for comparison.
* @param obj2
* Second object for comparison.
*
* @return <code>true</code> if objects are equal.
*/
public static<T> boolean equals(T obj1, T obj2)
{
if (obj1 == null && obj2 == null)
{
return true;
}
return (obj1 != null) ? obj1.equals(obj2) : obj2.equals(obj1);
}
/**
* Provides a simple unified API for delegating the querying of a
* data type-specific value from the directory.
*/
private static abstract class DirectoryHelper
{
/** Attribute name to obtain. */
protected String attrName = null;
/**
* Construct an instance and save the attribute name for future use.
*
* @param attrName
* The name of the attribute to obtain.
*/
public DirectoryHelper(String attrName)
{
this.attrName = attrName;
}
/**
* User-implemented method which queries the given directory service
* for the attribute value of the given node ID and the returns the
* result. The implementation of this method if data type-specific
* however the result is returned generically.
* <p>
* The attribute name provided at construction time is the one to use
* during the query. This eliminates the need to pass the attribute
* name to the code to which the call is delegated.
*
* @param ds
* A bound directory service instance. Must not be
* <code>null</code>.
* @param nodeId
* The full path of the node to obtain.
*
* @return Attribute value.
*/
public abstract Object getDirectoryNode(DirectoryService ds, String nodeId);
}
/**
* Returns a context-wide unique integer value which can be used as an ID.
* If the internal counter reaches the maximum value, it wraps around to 0.
* <p>
* @return next available integer value
*/
public static int uniqueId()
{
WorkArea wa = work.get();
int newValue = 0;
synchronized (wa)
{
newValue = ++wa.counter;
if (newValue == Integer.MAX_VALUE)
{
wa.counter = -1;
}
}
return newValue;
}
/**
* Set the project token for this context. Every access to directory will return values
* specific to this project. If a specific value is not found, the default/global one will be
* returned if it exist.
*
* The project can be set only once, so this method can be called only once for respective
* context.
*
* @param token
* the token that identifies the project.
*
* @throws IllegalArgumentException
* if the project was already set for current context.
*/
public static void setProjectToken(String token)
{
WorkArea workArea = work.get();
if ((workArea.projectToken == null && token == null) ||
(workArea.projectToken != null && workArea.projectToken.equals(token)))
{
// same token, nothing to do
return;
}
if (workArea.projectToken != null)
{
LOG.log(Level.WARNING,
"The project token can be set only once. Current token %s. Attempt to set token %s.",
workArea.projectToken,
token);
return;
}
workArea.projectToken = token;
}
/**
* Get the current project token. If it was not set, {@code null} is returned.
*
* @return the current project token or {@code null} if not previously set.
*/
public static String getProjectToken()
{
WorkArea workArea = work.get();
return workArea.projectToken;
}
/**
* Check if the two sets are the same. Note that <code>null</code> instances are allowed for
* either set.
*
* @param a
* The first set.
* @param b
* The second set.
*
* @return <code>true</code> if the sets match.
*/
public static<T> boolean isSame(Set<T> a, Set<T> b)
{
if (a == null)
{
return b == null;
}
else if (b == null)
{
return false;
}
return a.size() == b.size() && a.containsAll(b);
}
/**
* Check if a given value is a valid value.
*
* @param value
* Value to check.
* @param validValues
* List of valid values.
*
* @return <code>true</code> if value is in the list of valid values,
* otherwise <code>false</code>.
*/
public static boolean isValidValue(int value, int ... validValues)
{
for (int v : validValues)
{
if (value == v)
{
return true;
}
}
return false;
}
/**
* Determine if we are on the server or not.
*
* @return <code>true</code> if we are running on the server.
*/
public static boolean isServer()
{
SecurityManager sm = SecurityManager.getInstance();
return sm != null && sm.isServer();
}
/**
* Determine if we are on the client or not.
*
* @return <code>true</code> if we are running on the client.
*/
public static boolean isClient()
{
return !isServer();
}
/**
* Determine if we are on ChUI or not.
*
* @return <code>true</code> if we are running ChUI.
*/
public static boolean isChUI()
{
return isServer() ? LogicalTerminal.isChui() : ThinClient.getInstance().isChui();
}
/**
* Determine if we are on GUI or not.
*
* @return <code>true</code> if we are running GUI.
*/
public static boolean isGUI()
{
return !isChUI();
}
/**
* Determines whether two sets have common elements.
*
* @param <T>
*
* @param a
* a set
* @param b
* another set
*
* @return <code>true</code> if there are common elements
*/
public static<T> boolean intersect(Set<T> a, Set<T> b)
{
if (a == null || b == null || a.isEmpty() || b.isEmpty())
{
return false;
}
int aSz = a.size();
int bSz = b.size();
// iterate the smaller of the two sets
Iterator<T> iter = aSz <= bSz ? a.iterator() : b.iterator();
Set<T> comp = aSz <= bSz ? b : a;
while (iter.hasNext())
{
if (comp.contains(iter.next()))
{
return true;
}
}
return false;
}
/**
* Determines whether two sets have common elements.
*
* @param a
* a set
* @param b
* another set
*
* @return <code>true</code> if there are common elements
*/
public static boolean intersect(RoaringBitmap a, RoaringBitmap b)
{
if (a == null || b == null || a.isEmpty() || b.isEmpty())
{
return false;
}
int aSz = a.getCardinality();
int bSz = b.getCardinality();
// iterate the smaller of the two sets
Iterator<Integer> iter = aSz <= bSz ? a.iterator() : b.iterator();
RoaringBitmap comp = aSz <= bSz ? b : a;
while (iter.hasNext())
{
if (comp.contains(iter.next()))
{
return true;
}
}
return false;
}
/**
* Determines whether two sets have common elements.
*
* @param a
* a set
* @param b
* another set
*
* @return <code>true</code> if there are common elements
*/
public static boolean intersect(Roaring64Bitmap a, Roaring64Bitmap b)
{
if (a == null || b == null || a.isEmpty() || b.isEmpty())
{
return false;
}
int aSz = a.getIntCardinality();
int bSz = b.getIntCardinality();
// iterate the smaller of the two sets
Iterator<Long> iter = aSz <= bSz ? a.iterator() : b.iterator();
Roaring64Bitmap comp = aSz <= bSz ? b : a;
while (iter.hasNext())
{
if (comp.contains(iter.next()))
{
return true;
}
}
return false;
}
/**
* Check if the given instance contains all elements from the other instance.
*
* @param instance
* The first instance.
* @param other
* The other instance.
*
* @return <code>true</code> if the difference between the 'other' and 'instance' is empty.
*/
public static boolean containsAll(RoaringBitmap instance, RoaringBitmap other)
{
if (instance.getCardinality() < other.getCardinality())
{
return false;
}
Iterator<Integer> iter = other.iterator();
while (iter.hasNext())
{
if (!instance.contains(iter.next()))
{
return false;
}
}
return true;
}
/**
* Check if the given instance contains all elements from the other instance.
*
* @param instance
* The first instance.
* @param other
* The other instance.
*
* @return <code>true</code> if the difference between the 'other' and 'instance' is empty.
*/
public static boolean containsAll(Roaring64Bitmap instance, Roaring64Bitmap other)
{
if (instance.getIntCardinality() < other.getIntCardinality())
{
return false;
}
Iterator<Long> iter = other.iterator();
while (iter.hasNext())
{
if (!instance.contains(iter.next()))
{
return false;
}
}
return true;
}
/**
* Produces both the intersection and difference of two sets and puts those values into the
* given result set instances.
*
* @param <T>
* The type of the set elements.
* @param a
* a set
* @param b
* another set
* @param inter
* The result set which will contain the intersection results.
* @param diff
* The result set which will contain the difference results. This is the set of items in "a"
* which are not in "b", but it does not include items in "b" which are not in "a".
*/
public static<T> void split(Set<T> a, Set<T> b, Set<T> inter, Set<T> diff)
{
inter.clear();
diff.clear();
Iterator<T> iter = a.iterator();
T o = null;
while (iter.hasNext())
{
o = iter.next();
if (b.contains(o))
{
inter.add(o);
}
else
{
diff.add(o);
}
}
}
/**
* Produces the intersection of two sets and puts it into the third set.
*
* @param <T>
*
* @param a
* a set
* @param b
* another set
* @param result
* result set
*/
public static<T> void intersection(Set<T> a, Set<T> b, Set<T> result)
{
result.clear();
int aSz = a.size();
int bSz = b.size();
// iterate the smaller of the two sets
Iterator<T> iter = aSz <= bSz ? a.iterator() : b.iterator();
Set<T> comp = aSz <= bSz ? b : a;
while (iter.hasNext())
{
T t = iter.next();
if (comp.contains(t))
{
result.add(t);
}
}
}
/**
* Produces the difference of two sets and puts it into the third set.
*
* @param <T>
* The element type.
* @param a
* The first set.
* @param b
* The set to subtract from 'a'.
* @param result
* The result set which will contain the difference results. This is the set of items in "a"
* which are not in "b", but it does not include items in "b" which are not in "a".
*/
public static<T> void difference(Set<T> a, Set<T> b, Set<T> result)
{
result.clear();
Iterator<T> iter = a.iterator();
T o = null;
while (iter.hasNext())
{
o = iter.next();
if (!b.contains(o))
{
result.add(o);
}
}
}
/**
* Gets a resource path string to be cleared from invalid sequences and file separator duplication.
* Handles properly current('.') and uplevel('..') directory entries and empty spaces. No case
* letter changes are performing.
*
* @param nameDirty
* The not yet refined resource path string.
* @param separator
* The string representing file separator to use in result.
*
* @return Properly formatted case sensitive image path string.
*/
public static String getRefinedPathName(String nameDirty, String separator)
{
synchronized (getPathCache())
{
String cachedPath = getPathCache().get(nameDirty + separator);
if (cachedPath != null)
{
return cachedPath;
}
}
// parse the dirty path into array by separator to clean for multiple separators
String[] splitPath = nameDirty.split(separator);
int pathSize = splitPath.length;
// walk through the entries to clean up
for (int i = 0; i < pathSize; i++)
{
// process next path name entry
String name = splitPath[i];
// skip the current directory, empty directory, directory or files with more than 2 dots
// in beginning
if (name.equals(".") || name.isEmpty() || (name.startsWith("..") && !name.equals("..")))
{
// go one directory level up
for (int j = i; j < pathSize - 1; j++)
{
splitPath[j] = splitPath[j + 1];
}
// one entry has been removed for current dir
pathSize--;
i--;
continue;
}
// try to handle parent directory shift
if (name.equals(".."))
{
// for the first and last entries just ignore this - no parent directory
if (i > 0 && i < pathSize - 1)
{
// go one directory level up
for (int j = i; j < pathSize - 1; j++)
{
splitPath[j - 1] = splitPath[j + 1];
}
i--;
}
// two entries have been removed, one for '..' and parent directory
pathSize -= 2;
continue;
}
}
// second pass - build the final buffer
StringBuilder sb = new StringBuilder();
// at this time we must have clean array
for (int i = 0; i < pathSize; i++)
{
sb.append(splitPath[i]);
// for all entries except last add the separator
if (i < pathSize - 1)
{
sb.append(separator);
}
}
// fix the directory path
if (nameDirty.endsWith(separator) && !sb.toString().endsWith(separator))
{
sb.append(separator);
}
String result = sb.toString();
synchronized (getPathCache())
{
getPathCache().putIfAbsent(nameDirty + separator, result);
}
return result;
}
/**
* Get the path of the jar file containing the given class.
*
* @param clazz
* The given class
*
* @return The path of the jar file if it exists, otherwise null.
*/
public static String getJarPath(Class clazz)
{
CodeSource codeSource = clazz.getProtectionDomain().getCodeSource();
File jarFile;
try
{
if (codeSource.getLocation() != null)
{
jarFile = new File(codeSource.getLocation().toURI());
}
else
{
String path = clazz.getResource(clazz.getSimpleName() + ".class").getPath();
String jarFilePath = path.substring(path.indexOf(":") + 1, path.indexOf("!"));
jarFilePath = URLDecoder.decode(jarFilePath, "UTF-8");
jarFile = new File(jarFilePath);
}
}
catch (URISyntaxException | UnsupportedEncodingException e)
{
LOG.severe("", e);
return null;
}
return jarFile.getPath();
}
/**
* Produces the difference of two int arrays.
*
* @param a
* The first array.
* @param b
* The second array which is to be subtracted from the first.
*
* @return All elements from the first array that are not in the
* second array.
*/
public static int[] diffIntArrays(int[] a, int[] b)
{
// boundary cases
if (a.length == 0)
{
return new int[] {};
}
int[] aa = new int[a.length];
for (int i = 0; i < a.length; i ++)
{
aa[i] = a[i];
}
Arrays.sort(aa);
if (b.length == 0)
{
return aa;
}
int[] bb = new int[b.length];
for (int i = 0; i < b.length; i ++)
{
bb[i] = b[i];
}
Arrays.sort(bb);
int[] res = new int[aa.length];
int count = 0;
int i = 0;
int j = 0;
scan:
for (i = 0; i < aa.length; i ++)
{
// equal numbers; skip both
if (aa[i] == bb[j])
{
j ++;
if (j < bb.length)
{
continue;
}
break scan;
}
// integer from a to keep
if (a[i] < b[j])
{
res[count ++] = a[i];
continue;
}
// integer from b to skip
while (a[i] > b[j])
{
j ++;
if (j < bb.length)
{
continue;
}
break scan;
}
}
// copy the rest of a
for (int k = i + 1; k < aa.length; k ++)
{
res[count ++] = aa[k];
}
// compact the array
int[] diff = new int[count];
for (i = 0; i < count; i ++)
{
diff[i] = res[i];
}
return diff;
}
/**
* The method returns a reverse {@linkplain java.util.stream.Stream}
* from the supplied <code>array</code>.
*
* @param array
* The array to be reversed in a stream.
*
* @return see above.
*/
public static <T> java.util.stream.Stream<T> reverseStream(T[] array)
{
int size = array.length;
return IntStream.rangeClosed(0, size - 1)
.mapToObj(i -> array[size - i - 1]);
}
/**
* The method returns a reverse {@linkplain java.util.stream.Stream}
* from the supplied linked list.
*
* @param list
* The linked list to be reversed in a stream.
*
* @return see above.
*/
public static <T> java.util.stream.Stream<T> reverseStream(LinkedList<T> list)
{
Iterator<T> it = list.descendingIterator();
Spliterator<T> sit = Spliterators.spliteratorUnknownSize(it, Spliterator.ORDERED);
return StreamSupport.stream(sit, false);
}
/**
* The method returns a reverse {@linkplain java.util.stream.Stream}
* from the supplied <code>list</code>.
*
* @param list
* The list to be reversed in a stream.
*
* @return see above.
*/
public static <T> java.util.stream.Stream<T> reverseStream(List<T> list)
{
if (list instanceof LinkedList<?>)
{
return reverseStream((LinkedList<T>) list);
}
else
{
int size = list.size();
return IntStream.rangeClosed(0, size - 1)
.mapToObj(i -> list.get(size - i - 1));
}
}
/**
* The method returns the first non-null argument from the supplied array of
* arguments.
*
* @param args
* An array of arguments.
*
* @return See above.
*
* @throws IllegalArgumentException
* when zero arguments is passed to the method.
* @throws NullPointerException
* when no non-null value is found in the supplied array of arguments.
*/
@SafeVarargs
public static <T> T nonNull(T... args)
{
if (args == null || args.length == 0)
{
throw new IllegalArgumentException();
}
for (T item : args)
{
if (item != null)
{
return item;
}
}
throw new NullPointerException();
}
/**
* The method returns the first non-empty trimmed string represented by the supplied
* arguments. {@linkplain Object#toString()} is used to convert each argument to a
* string value and trimmed by {@linkplain String#trim()}.
*
* @param args
* An array of arguments.
*
* @return See above.
*
* @throws IllegalArgumentException
* when zero arguments is passed to the method or no non-empty string is
* represented by any of the supplied arguments.
*/
@SafeVarargs
public static String nonEmpty(Object... args)
{
if (args == null || args.length == 0)
{
throw new IllegalArgumentException();
}
for (Object item : args)
{
if (item == null)
{
continue;
}
String str = item.toString().trim();
if (!str.isEmpty())
{
return str;
}
}
throw new IllegalArgumentException();
}
/**
* Reorder widgets according to their ZOrderClass retaining the order within the class.
*
* @param widgets
* The widget collection
* @param zorder
* The given z-order function defined on widgets
*
* @return The modified resorted widgets collection.
*/
public static <W> Collection<W> normalizeZOrder(Collection<W> widgets,
Function<W, ZOrderClass> zorder)
{
Map<ZKey, W> m = new TreeMap<ZKey, W>();
int i = 0;
for (W w: widgets)
{
m.put(new ZKey(zorder.apply(w), i++), w);
}
widgets.clear();
widgets.addAll(m.values());
return widgets;
}
/**
* Move the specified widget to the bottom of the widget list considering Z-order class.
* @param <W>
*
* @param widgets
* The given widgets list.
* @param widget
* The given widget to move.
* @param zorder
* function to get ZOrderClass for the widget
*
* @return True if the given widget is changed its position in the given list.
*/
public static <W> boolean moveToBottomInClass(List<W> widgets,
W widget,
Function<W, ZOrderClass> zorder)
{
final int pos = widgets.indexOf(widget);
if (pos < 0)
{
return false;
}
// remove it now, it will always be re-added
widgets.remove(pos);
final int size = widgets.size();
final int cls = zorder.apply(widget).ordinal();
int limit = size;
int np = size;
boolean found = false;
// find first widget in the same z-class and move it in its location (before it);
// if there are no widgets in the same z-class, then put it on top
for (int i = 0; i < limit; i++)
{
final W w = widgets.get(i);
int wcls = zorder.apply(w).ordinal();
if (wcls == cls)
{
found = true;
np = i;
break;
}
}
// move it to the correct location (it may be the same)
if (found)
{
widgets.add(np, widget);
}
else
{
widgets.add(0, widget);
}
// this takes care of re-ordering the widgets by their z-class (keeping the order in each
// specific class)
normalizeZOrder(widgets, zorder);
// return true only if the widget's position has moved
return np != pos;
}
/**
* Move specified widget to the top of the given widget list considering Z-order class.
* @param <W>
*
* @param widgets
* The given widgets list.
* @param widget
* The given widget to move.
* @param zorder
* function to get ZOrderClass for the widget
*
* @return True if the given widget is changed its position in the given list.
*/
public static <W> boolean moveToTopInClass(List<W> widgets,
W widget,
Function<W, ZOrderClass> zorder)
{
int pos = widgets.indexOf(widget);
if (pos < 0)
{
return false;
}
// remove it now, it will always be re-added
widgets.remove(pos);
int size = widgets.size();
int cls = zorder.apply(widget).ordinal();
int np = size;
boolean found = false;
// find last widget in the same z-class and move it after it; if there are no widgets
// in the same z-class, then put it on top
for (int i = size - 1; i >= 0; i--)
{
W w = widgets.get(i);
int wcls = zorder.apply(w).ordinal();
if (wcls == cls)
{
// found the new class, keep it
found = true;
np = i;
break;
}
}
if (found)
{
// insert widget at this position
np = np + 1;
}
// move it to the correct location (it may be the same)
if (np >= size)
{
widgets.add(widget);
}
else
{
widgets.add(np, widget);
}
// this takes care of re-ordering the widgets by their z-class (keeping the order in each
// specific class)
normalizeZOrder(widgets, zorder);
// return true only if the widget's position has moved
return np != pos;
}
/**
* Remove all items from that collection which satisfy the given condition
*
* @param c
* the collection
* @param p
* the condition
*/
public static <T> void removeAllIf(Collection<T> c, Predicate<T> p)
{
if (c == null)
{
return;
}
for (Iterator<T> iter = c.iterator(); iter.hasNext(); )
{
if (p.test(iter.next()))
{
iter.remove();
}
}
}
/**
* Converts string size value to integer one. The string can be suffixed with MB or KB or none
* for megabyes base init. The input string must be non null and not empty.
*
* @param sizeStr
* An string representing the integer limit value.
*
* @return see above.
*/
public static int getSizeLimitFromString(String sizeStr)
{
// default unit will be bytes
int res = 1;
// prepare string
sizeStr = sizeStr.toUpperCase().trim();
// analyze the string
StringBuilder sb = new StringBuilder();
for (int i = 0; i < sizeStr.length(); i++)
{
char chNext = sizeStr.charAt(i);
if (Character.isDigit(chNext))
{
sb.append(chNext);
}
else
{
if (chNext == 'M')
{
// the base units are megabytes
res = 1024 * 1024;
break;
}
else if (chNext == 'K')
{
// the base units are kilobytes
res = 1024;
break;
}
else
{
// ignore other chars
continue;
}
}
}
// now string builder must have only numbers
res *= Integer.valueOf(sb.toString()).intValue();
return res;
}
/**
* Converts long number to little-endian bytes.
*
* @param l
* A long number.
*
* @return the returned byte array.
*/
public static byte[] longToBytesLE(long l)
{
return new byte[]
{
(byte) l,
(byte) (l >> 8),
(byte) (l >> 16),
(byte) (l >> 24),
(byte) (l >> 32),
(byte) (l >> 40),
(byte) (l >> 48),
(byte) (l >> 56)
};
}
/**
* Converts little-endian bytes to long number.
*
* @param data
* Bytes to convert.
*
* @return the returned long.
*/
public static long bytesToLongLE(byte[] data)
{
long value = data[0] & 0xffL;
value += (data[1] & 0xffL) << 8;
value += (data[2] & 0xffL) << 16;
value += (data[3] & 0xffL) << 24;
value += (data[4] & 0xffL) << 32;
value += (data[5] & 0xffL) << 40;
value += (data[6] & 0xffL) << 48;
value += (data[7] & 0xffL) << 56;
return value;
}
/**
* Find a field with the specified value, in given class.
*
* @param instance
* The instance where to look for the field.
* @param cls
* The field is defined in this class or one of its super-classes.
* @param val
* The field must have this exact value, to be found.
*
* @return The found field, or <code>null</code> if no field is holding the expected value.
*/
public static Field findFieldWithValue(Object instance, Class<?> cls, Object val)
{
if (cls == null)
{
return null;
}
Field[] fields = cls.getDeclaredFields();
for (Field f : fields)
{
f.setAccessible(true);
try
{
Object fval = f.get(instance);
if (fval instanceof ContextLocal)
{
fval = ((ContextLocal) fval).get();
}
if (val == fval)
{
return f;
}
}
catch (Exception e)
{
LOG.warning("", e);
}
}
return findFieldWithValue(instance, cls.getSuperclass(), val);
}
/**
* Collect all the implemented interfaces (directly and from the hierarchy), for the given
* class.
* <p>
* The collection is performed doing a BFS walk of the hierarchy, starting from the given class.
*
* @param cls
* The class from where to get the interfaces.
* @param interfaces
* A set where to store the interfaces, in the BFS order..
*/
public static void collectInterfaces(Class<?> cls, Set<Class<?>> interfaces)
{
collectInterfacesImpl(cls, interfaces, new HashSet<>());
}
/**
* Do a DFS walk of the class hierarchy and execute the given task when going 'back up' on the tree.
*
* @param cls
* The class to start the walk.
* @param task
* The task to execute.
*/
public static void dfsClassHierarchy(Class<?> cls, Consumer<Class<?>> task)
{
Class<?>[] ifcs = cls.getInterfaces();
if (ifcs != null)
{
for (Class<?> ifc : ifcs)
{
dfsClassHierarchy(ifc, task);
}
}
if (cls.getSuperclass() != null)
{
dfsClassHierarchy(cls.getSuperclass(), task);
}
task.accept(cls);
}
/**
* Reads a row of CSV values from the reader and returns the values as a Java list.
*
* @param r
* Reader to read from.
*
* @return A valid list or {@code null} if end of stream is reached.
*
* @throws IOException
*/
public static List<String> readCSVLine(Reader r)
throws IOException
{
int ch = r.read();
// skip CR
while (ch == '\r')
{
ch = r.read();
}
if (ch < 0)
{
return null;
}
List<String> res = new ArrayList<>();
StringBuffer curVal = new StringBuffer();
boolean quotes = false;
boolean inval = false;
while (ch >= 0)
{
if (quotes)
{
inval = true;
if (ch == '\"')
{
quotes = false;
}
else
{
curVal.append((char) ch);
}
}
else
{
if (ch == '\"')
{
quotes = true;
if (inval)
{
curVal.append('\"');
}
}
else if (ch == ',')
{
res.add(curVal.toString());
curVal = new StringBuffer();
inval = false;
}
else if (ch == '\r')
{
// skip
}
else if (ch == '\n')
{
break;
}
else
{
curVal.append((char) ch);
}
}
ch = r.read();
}
res.add(curVal.toString());
return res;
}
/**
* The method disables validation of SSL certificates when initiating HTTPS client connections
* using java.net facility. The method creates new SSL Socket Factory and Host Name Verifier and
* sets them as the defaults in {@linkplain HttpsURLConnection}.
* <p>
* All connections initiated with {@linkplain HttpsURLConnection} will be made without certificate
* validations! Do not use on production systems!
*/
public static void disableClientSSLCertificateValidation()
{
// do not validate certificate chains
TrustManager[] trustManager = new TrustManager[]
{
new X509TrustManager()
{
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers()
{
return null;
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
{
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
{
}
}
};
try
{
// install socket factory accepting all certificates
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustManager, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
// accept all hosts
HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true);
LOG.log(Level.WARNING, "!!! Disabled SSL certificate validation for outgoing HTTPS connections. " +
"This is intended for test environments only !!! ");
}
catch (Exception ex)
{
LOG.log(Level.SEVERE, "Failure during HttpsURLConnection setup!", ex);
}
}
/**
* Poll all elements of a Queue and return them in the same order in a List. Useful when the elements
* have to be returned and the Queue cleared without losing any newly added elements.
*
* @param queue
* A queue of T elements.
* @param <T>
* The type of elements in the Queue.
*
* @return Empty Optional when the queue is empty or Optional of List with all the elements in the queue.
*/
public static <T> Optional<List<T>> pollAll(Queue<T> queue)
{
if (queue == null || queue.isEmpty())
{
return Optional.empty();
}
List<T> elements = new ArrayList<>(queue.size());
while (!queue.isEmpty())
{
elements.add(queue.poll());
}
return Optional.of(elements);
}
/**
* Get a simplified string which describes the current context. This is
* intended primarily for naming a thread.
*
* @param descr
* Description of the thread's purpose.
*
* @return Simple description of context.
*/
public static String createThreadName(String descr)
{
SecurityManager sm = SecurityManager.getInstance();
String txt = null;
if (sm != null && SecurityManager.initialized() && sm.contextSm.hasContext())
{
txt = String.format("%s [%08X:%s]",
descr,
sm.sessionSm.getSessionId(),
sm.getUserId());
}
return txt;
}
/**
* Returns the configured legacy root package as a path on the classpath (starts and ends with Unix style
* file separator "/").
*
* @return See above.
*/
public static String getLegacyPkgRootAsJarPath()
{
String packagePath = packageToJarPath(SourceNameMapper.getPackageRoot());
if (!packagePath.startsWith("/"))
{
packagePath = "/" + packagePath;
}
return packagePath;
}
/**
* Returns the java package name as a path on the classpath (starts and ends with Unix style file
* separator "/").
*
* @param packageName
* The package name.
*
* @return See above.
*/
public static String packageToJarPath(String packageName)
{
String packagePath = packageName.replace('.', '/');
if (!packagePath.endsWith("/"))
{
packagePath = packagePath + "/";
}
return packagePath;
}
/**
* Logs the error stream for the process.
*
* @param logger
* The logger.
* @param process
* The process.
* @param processIdentifier
* The identifier of the process, used for the log.
*/
public static void logProcessError(CentralLogger logger, Process process, String processIdentifier)
{
try (BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream())))
{
List<String> lines = new ArrayList<>();
String line;
// without ready check, sometimes the read freezes
while (errorReader.ready() && (line = errorReader.readLine()) != null)
{
lines.add(line);
}
String error = String.join("", lines);
if (StringHelper.hasContent(error))
{
logger.warning("Process for '" + processIdentifier + "' returned error: " + error);
}
}
catch (Throwable t)
{
logger.warning("Process error stream for '" + processIdentifier + "' can't be handled.", t);
}
}
/**
* Collect all the implemented interfaces (directly and from the hierarchy), for the given
* class.
* <p>
* The collection is performed doing a BFS walk of the hierarchy, starting from the given class.
*
* @param cls
* The class from where to get the interfaces.
* @param interfaces
* A set where to store the interfaces, in the BFS order.
* @param collected
* A set of interfaces already processed.
*/
private static void collectInterfacesImpl(Class<?> cls, Set<Class<?>> interfaces, Set<Class<?>> collected)
{
if (collected.contains(cls))
{
return;
}
Class<?>[] ifcs = cls.getInterfaces();
if (ifcs != null)
{
for (Class<?> ifc : ifcs)
{
interfaces.add(ifc);
}
for (Class<?> ifc : ifcs)
{
collectInterfacesImpl(ifc, interfaces, collected);
collected.add(ifc);
}
}
if (cls.getSuperclass() != null)
{
collectInterfacesImpl(cls.getSuperclass(), interfaces, collected);
}
}
/**
* A method that returns the pathCache, if already initialized,
* otherwise, initializes it.
*
* @return See above.
*/
private static ExpiryCache<String, String> getPathCache()
{
if (pathCache == null)
{
pathCache = CacheManager.createLRUCache(Utils.class, "pathCache", 131072);
}
return pathCache;
}
/**
*
* Utility class extending HashMap functionality
*
* @param <K>
* class of the key
* @param <V>
* class of the value
*/
public static class InstrumentedMap<K,V> extends HashMap<K,V>
{
/** the value supplier */
private Supplier<V> valueSupplier;
/**
* Constructor
*
* @param valueSupplier
* the value supplier
*/
public InstrumentedMap(Supplier<V> valueSupplier)
{
super();
this.valueSupplier = valueSupplier;
}
/**
* Modify the value of a map creating it if absent
*
* @param key
* the key
* @param modifier
* the value modifier
*
* @return the value
*/
public V modifyValue(K key, Consumer<V> modifier)
{
V value = get(key);
if (value == null)
{
put(key, value = valueSupplier.get());
}
modifier.accept(value);
return value;
}
}
/**
* Stores global data relating to the state of the current context.
*/
private static class WorkArea
{
/** Internal counter tha last used value */
private int counter = -1;
/**
* Project token. This is used for per-project directory settings in case multiple projects
* are 'parked' within the same P2J installation and setting are different.
*/
private String projectToken = null;
/** The explicitly set temporary directory coming from the StartupParameters */
private String temporaryDirectory = null;
/** The name of the FWD client's temporary folder. */
private String defaultFwdTempFolder = "fwd" + new Random().nextLong();
}
/**
* Simple container that stores and returns a context-local instance of
* the global work area.
*/
private static class ContextContainer
extends ContextLocal<WorkArea>
{
/**
* Initializes the work area, the first time it is requested within a
* new context.
*
* @return The newly instantiated work area.
*/
@Override
protected WorkArea initialValue()
{
WorkArea wa = new WorkArea();
return wa;
}
}
/**
* Auxiliary class for use on the normalizeOrder method
*/
private static class ZKey implements Comparable<ZKey>
{
/** Z-order class of the widget */
private final ZOrderClass cls;
/** initial position of the widget */
private final int idx;
/**
* Constructor
*
* @param cls
* Z-order class of the widget
* @param idx
* initial position of the widget
*/
public ZKey(ZOrderClass cls, int idx)
{
super();
this.cls = cls;
this.idx = idx;
}
/**
* The implementation of the {@link java.lang.Comparable#compareTo} method
*/
@Override
public int compareTo(ZKey other)
{
return this.cls.compareTo(other.cls) == 0 ?
this.idx - other.idx :
-this.cls.compareTo(other.cls);
}
}
/**
* Global ReflectASM data for {@link Utils#invoke(Method, Object, Object...)}.
*/
private static class MethodData
{
/** The ReflectASM method access class. */
private final MethodAccess access;
/** Cache of method indices for each {@link Method} instance. */
private final Map<Method, Integer> methodAccessIdx;
/***
* Create a new instance and populate the method access index.
*
* @param cls
* The type which needs to be initialized and cached.
*/
public MethodData(Class<?> cls)
{
this.access = MethodAccess.get(cls);
Map<Method, Integer> midx = new HashMap<>();
String[] mnames = access.getMethodNames();
Class<?>[][] mpars = access.getParameterTypes();
for (int i = 0; i < mnames.length; i++)
{
try
{
Method method = null;
Class<?> type = cls;
do
{
method = cls.getDeclaredMethod(mnames[i], mpars[i]);
type = type.getSuperclass();
}
while (method == null);
midx.put(method, access.getIndex(method.getName(), method.getParameterTypes()));
}
catch (NoSuchMethodException |
SecurityException e)
{
// ignore
}
}
methodAccessIdx = midx;
}
}
}