/* * The contents of this file are subject to the terms of the Common Development and * Distribution License (the License). You may not use this file except in compliance with the * License. * * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the * specific language governing permission and limitations under the License. * * When distributing Covered Software, include this CDDL Header Notice in each file and include * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL * Header, with the fields enclosed by brackets [] replaced by your own identifying * information: "Portions Copyright [year] [name of copyright owner]". * * Copyright 2012-2015 ForgeRock AS. */ package org.forgerock.opendj.rest2ldap; import static javax.xml.bind.DatatypeConverter.parseDateTime; import static javax.xml.bind.DatatypeConverter.printDateTime; import static org.forgerock.opendj.ldap.Filter.alwaysFalse; import static org.forgerock.opendj.ldap.Functions.byteStringToBoolean; import static org.forgerock.opendj.ldap.Functions.byteStringToGeneralizedTime; import static org.forgerock.opendj.ldap.Functions.byteStringToLong; import static org.forgerock.opendj.ldap.Functions.byteStringToString; import static org.forgerock.opendj.ldap.schema.CoreSchema.getBooleanSyntax; import static org.forgerock.opendj.ldap.schema.CoreSchema.getGeneralizedTimeSyntax; import static org.forgerock.opendj.ldap.schema.CoreSchema.getIntegerSyntax; import static org.forgerock.opendj.rest2ldap.Rest2LDAP.asResourceException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.concurrent.atomic.AtomicInteger; import org.forgerock.json.fluent.JsonValue; import org.forgerock.json.resource.ResourceException; import org.forgerock.json.resource.ResultHandler; import org.forgerock.opendj.ldap.Attribute; import org.forgerock.opendj.ldap.AttributeDescription; import org.forgerock.opendj.ldap.ByteString; import org.forgerock.opendj.ldap.Filter; import org.forgerock.opendj.ldap.GeneralizedTime; import org.forgerock.opendj.ldap.LinkedAttribute; import org.forgerock.opendj.ldap.schema.Syntax; import org.forgerock.util.Function; import org.forgerock.util.promise.NeverThrowsException; /** * Internal utility methods. */ final class Utils { /** * Implementation class for {@link #accumulate}. * * @param * The type of result. */ private static final class AccumulatingResultHandler implements ResultHandler { /** Guarded by latch. */ private ResourceException exception; private final ResultHandler> handler; private final AtomicInteger latch; private final List results; private AccumulatingResultHandler(final int size, final ResultHandler> handler) { if (size < 0) { throw new IllegalStateException(); } this.latch = new AtomicInteger(size); this.results = new ArrayList<>(size); this.handler = handler; if (size == 0) { // Invoke immediately. handler.handleResult(results); } } @Override public void handleError(final ResourceException e) { exception = e; latch(); // Volatile write publishes exception. } @Override public void handleResult(final V result) { if (result != null) { synchronized (results) { results.add(result); } } latch(); } private void latch() { /* * Invoke the handler once all results have been received. Avoid * failing-fast when an error occurs because some in-flight tasks * may depend on resources (e.g. connections) which are * automatically closed on completion. */ if (latch.decrementAndGet() == 0) { if (exception != null) { handler.handleError(exception); } else { handler.handleResult(results); } } } } private static final Function BASE64_TO_BYTESTRING = new Function() { @Override public ByteString apply(final Object value) { return ByteString.valueOfBase64(String.valueOf(value)); } }; private static final Function BYTESTRING_TO_BASE64 = new Function() { @Override public String apply(final ByteString value) { return value.toBase64String(); } }; /** * Returns a result handler which can be used to collect the results of * {@code size} asynchronous operations. Once all results have been received * {@code handler} will be invoked with a list containing the results. * Accumulation ignores {@code null} results, so the result list may be * smaller than {@code size}. The returned result handler does not * fail-fast: it will wait until all results have been received even if an * error has been detected. This ensures that asynchronous operations can * use resources such as connections which are automatically released * (closed) upon completion of the final operation. * * @param * The type of result to be collected. * @param size * The number of expected results. * @param handler * The result handler to be invoked when all results have been * received. * @return A result handler which can be used to collect the results of * {@code size} asynchronous operations. */ static ResultHandler accumulate(final int size, final ResultHandler> handler) { return new AccumulatingResultHandler<>(size, handler); } static Object attributeToJson(final Attribute a) { final Function f = byteStringToJson(a.getAttributeDescription()); final boolean isSingleValued = a.getAttributeDescription().getAttributeType().isSingleValue(); return isSingleValued ? a.parse().as(f) : asList(a.parse().asSetOf(f)); } static Function base64ToByteString() { return BASE64_TO_BYTESTRING; } static Function byteStringToBase64() { return BYTESTRING_TO_BASE64; } static Function byteStringToJson(final AttributeDescription ad) { return new Function() { @Override public Object apply(final ByteString value) { final Syntax syntax = ad.getAttributeType().getSyntax(); if (syntax.equals(getBooleanSyntax())) { return byteStringToBoolean().apply(value); } else if (syntax.equals(getIntegerSyntax())) { return byteStringToLong().apply(value); } else if (syntax.equals(getGeneralizedTimeSyntax())) { return printDateTime(byteStringToGeneralizedTime().apply(value) .toCalendar()); } else { return byteStringToString().apply(value); } } }; } static T ensureNotNull(final T object) { if (object == null) { throw new NullPointerException(); } return object; } static T ensureNotNull(final T object, final String message) { if (object == null) { throw new NullPointerException(message); } return object; } static String getAttributeName(final Attribute a) { return a.getAttributeDescription().withoutOption("binary").toString(); } /** * Stub formatter for i18n strings. * * @param format * The format string. * @param args * The string arguments. * @return The formatted string. */ static String i18n(final String format, final Object... args) { return String.format(format, args); } static boolean isJSONPrimitive(final Object value) { return value instanceof String || value instanceof Boolean || value instanceof Number; } static boolean isNullOrEmpty(final JsonValue v) { return v == null || v.isNull() || (v.isList() && v.size() == 0); } static Attribute jsonToAttribute(final Object value, final AttributeDescription ad) { return jsonToAttribute(value, ad, jsonToByteString(ad)); } static Attribute jsonToAttribute(final Object value, final AttributeDescription ad, final Function f) { if (isJSONPrimitive(value)) { return new LinkedAttribute(ad, f.apply(value)); } else if (value instanceof Collection) { final Attribute a = new LinkedAttribute(ad); for (final Object o : (Collection) value) { a.add(f.apply(o)); } return a; } else { throw new IllegalArgumentException("Unrecognized type of JSON value: " + value.getClass().getName()); } } static Function jsonToByteString(final AttributeDescription ad) { return new Function() { @Override public ByteString apply(final Object value) { if (isJSONPrimitive(value)) { final Syntax syntax = ad.getAttributeType().getSyntax(); if (syntax.equals(getGeneralizedTimeSyntax())) { return ByteString.valueOf(GeneralizedTime.valueOf(parseDateTime(value.toString()))); } else { return ByteString.valueOf(value); } } else { throw new IllegalArgumentException("Unrecognized type of JSON value: " + value.getClass().getName()); } } }; } static Filter toFilter(final boolean value) { return value ? Filter.alwaysTrue() : Filter.alwaysFalse(); } static Filter toFilter(final Context c, final FilterType type, final String ldapAttribute, final ByteString valueAssertion) { final Filter filter; switch (type) { case CONTAINS: filter = Filter.substrings(ldapAttribute, null, Collections.singleton(valueAssertion), null); break; case STARTS_WITH: filter = Filter.substrings(ldapAttribute, valueAssertion, null, null); break; case EQUAL_TO: filter = Filter.equality(ldapAttribute, valueAssertion); break; case GREATER_THAN: filter = Filter.greaterThan(ldapAttribute, valueAssertion); break; case GREATER_THAN_OR_EQUAL_TO: filter = Filter.greaterOrEqual(ldapAttribute, valueAssertion); break; case LESS_THAN: filter = Filter.lessThan(ldapAttribute, valueAssertion); break; case LESS_THAN_OR_EQUAL_TO: filter = Filter.lessOrEqual(ldapAttribute, valueAssertion); break; case PRESENT: filter = Filter.present(ldapAttribute); break; case EXTENDED: default: filter = alwaysFalse(); // Not supported. break; } return filter; } static String toLowerCase(final String s) { return s != null ? s.toLowerCase(Locale.ENGLISH) : null; } /** * Returns a result handler which accepts results of type {@code M}, applies * the function {@code f} in order to convert the result to an object of * type {@code N}, and subsequently invokes {@code handler}. If an * unexpected error occurs while performing the transformation, the * exception is converted to a {@code ResourceException} before invoking * {@code handler.handleError()}. * * @param * The type of result expected by the returned handler. * @param * The type of result expected by {@code handler}. * @param f * A function which converts the result of type {@code M} to type * {@code N}. * @param handler * A result handler which accepts results of type {@code N}. * @return A result handler which accepts results of type {@code M}. */ static ResultHandler transform(final Function f, final ResultHandler handler) { return new ResultHandler() { @Override public void handleError(final ResourceException error) { handler.handleError(error); } @Override public void handleResult(final M result) { try { handler.handleResult(f.apply(result)); } catch (final Throwable t) { handler.handleError(asResourceException(t)); } } }; } private static List asList(final Collection c) { if (c instanceof List) { return (List) c; } return new ArrayList<>(c); } /** Prevent instantiation. */ private Utils() { // No implementation required. } }