001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2012-2016 ForgeRock AS. 015 */ 016package org.forgerock.opendj.rest2ldap; 017 018import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*; 019 020import java.util.ArrayList; 021import java.util.Collection; 022import java.util.List; 023import java.util.Set; 024 025import org.forgerock.json.JsonPointer; 026import org.forgerock.json.JsonValue; 027import org.forgerock.json.resource.ResourceException; 028import org.forgerock.opendj.ldap.Attribute; 029import org.forgerock.opendj.ldap.AttributeDescription; 030import org.forgerock.opendj.ldap.ByteString; 031import org.forgerock.opendj.ldap.Connection; 032import org.forgerock.opendj.ldap.Entry; 033import org.forgerock.opendj.ldap.Filter; 034import org.forgerock.util.Function; 035import org.forgerock.util.promise.NeverThrowsException; 036import org.forgerock.util.promise.Promise; 037 038import static java.util.Collections.*; 039 040import static org.forgerock.opendj.ldap.Filter.*; 041import static org.forgerock.opendj.rest2ldap.Rest2Ldap.asResourceException; 042import static org.forgerock.opendj.rest2ldap.Utils.*; 043import static org.forgerock.util.promise.Promises.newResultPromise; 044 045/** An property mapper which provides a simple mapping from a JSON value to a single LDAP attribute. */ 046public final class SimplePropertyMapper extends AbstractLdapPropertyMapper<SimplePropertyMapper> { 047 private Function<ByteString, ?, NeverThrowsException> decoder; 048 private Function<Object, ByteString, NeverThrowsException> encoder; 049 050 SimplePropertyMapper(final AttributeDescription ldapAttributeName) { 051 super(ldapAttributeName); 052 } 053 054 /** 055 * Sets the decoder which will be used for converting LDAP attribute values 056 * to JSON values. 057 * 058 * @param f 059 * The function to use for decoding LDAP attribute values. 060 * @return This property mapper. 061 */ 062 public SimplePropertyMapper decoder(final Function<ByteString, ?, NeverThrowsException> f) { 063 this.decoder = f; 064 return this; 065 } 066 067 /** 068 * Sets the default JSON value which should be substituted when the LDAP attribute is not found in the LDAP entry. 069 * 070 * @param defaultValue 071 * The default JSON value. 072 * @return This property mapper. 073 */ 074 public SimplePropertyMapper defaultJsonValue(final Object defaultValue) { 075 this.defaultJsonValues = defaultValue != null ? singletonList(defaultValue) : emptyList(); 076 return this; 077 } 078 079 /** 080 * Sets the default JSON values which should be substituted when the LDAP attribute is not found in the LDAP entry. 081 * 082 * @param defaultValues 083 * The default JSON values. 084 * @return This property mapper. 085 */ 086 public SimplePropertyMapper defaultJsonValues(final Collection<?> defaultValues) { 087 this.defaultJsonValues = defaultValues != null ? new ArrayList<>(defaultValues) : emptyList(); 088 return this; 089 } 090 091 /** 092 * Sets the encoder which will be used for converting JSON values to LDAP 093 * attribute values. 094 * 095 * @param f 096 * The function to use for encoding LDAP attribute values. 097 * @return This property mapper. 098 */ 099 public SimplePropertyMapper encoder(final Function<Object, ByteString, NeverThrowsException> f) { 100 this.encoder = f; 101 return this; 102 } 103 104 /** 105 * Indicates that JSON values are base 64 encodings of binary data. Calling 106 * this method with the value {@code true} is equivalent to the following: 107 * 108 * <pre> 109 * mapper.decoder(...); // function that converts binary data to base 64 110 * mapper.encoder(...); // function that converts base 64 to binary data 111 * </pre> 112 * 113 * Passing in a value of {@code false} resets the encoding and decoding 114 * functions to the default. 115 * 116 * @param isBinary {@code true} if this property is binary. 117 * @return This property mapper. 118 */ 119 public SimplePropertyMapper isBinary(final boolean isBinary) { 120 if (isBinary) { 121 decoder = byteStringToBase64(); 122 encoder = base64ToByteString(); 123 } else { 124 decoder = null; 125 encoder = null; 126 } 127 return this; 128 } 129 130 @Override 131 public String toString() { 132 return "simple(" + ldapAttributeName + ")"; 133 } 134 135 @Override 136 Promise<Filter, ResourceException> getLdapFilter(final Connection connection, final Resource resource, 137 final JsonPointer path, final JsonPointer subPath, 138 final FilterType type, final String operator, 139 final Object valueAssertion) { 140 if (subPath.isEmpty()) { 141 try { 142 final ByteString va = valueAssertion != null ? encoder().apply(valueAssertion) : null; 143 return newResultPromise(toFilter(type, ldapAttributeName.toString(), va)); 144 } catch (final Exception e) { 145 // Invalid assertion value - bad request. 146 return newBadRequestException( 147 ERR_ILLEGAL_FILTER_ASSERTION_VALUE.get(String.valueOf(valueAssertion), path), e).asPromise(); 148 } 149 } else { 150 // This property mapper does not support partial filtering. 151 return newResultPromise(alwaysFalse()); 152 } 153 } 154 155 @Override 156 Promise<Attribute, ResourceException> getNewLdapAttributes(final Connection connection, final Resource resource, 157 final JsonPointer path, final List<Object> newValues) { 158 try { 159 return newResultPromise(jsonToAttribute(newValues, ldapAttributeName, encoder())); 160 } catch (final Exception ex) { 161 return newBadRequestException(ERR_ENCODING_VALUES_FOR_FIELD.get(path, ex.getMessage())).asPromise(); 162 } 163 } 164 165 @Override 166 SimplePropertyMapper getThis() { 167 return this; 168 } 169 170 @SuppressWarnings("fallthrough") 171 @Override 172 Promise<JsonValue, ResourceException> read(final Connection connection, final Resource resource, 173 final JsonPointer path, final Entry e) { 174 try { 175 final Set<Object> s = e.parseAttribute(ldapAttributeName).asSetOf(decoder(), defaultJsonValues); 176 switch (s.size()) { 177 case 0: 178 return newResultPromise(null); 179 case 1: 180 if (attributeIsSingleValued()) { 181 return newResultPromise(new JsonValue(s.iterator().next())); 182 } 183 // Fall-though: unexpectedly got multiple values. It's probably best to just return them. 184 default: 185 return newResultPromise(new JsonValue(new ArrayList<>(s))); 186 } 187 } catch (final Exception ex) { 188 // The LDAP attribute could not be decoded. 189 return asResourceException(ex).asPromise(); 190 } 191 } 192 193 private Function<ByteString, ?, NeverThrowsException> decoder() { 194 return decoder == null ? byteStringToJson(ldapAttributeName) : decoder; 195 } 196 197 private Function<Object, ByteString, NeverThrowsException> encoder() { 198 return encoder == null ? jsonToByteString(ldapAttributeName) : encoder; 199 } 200 201}