View Javadoc
1   /*
2    * Copyright 2019 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.bremersee.data.ldaptive;
18  
19  import java.util.Optional;
20  import lombok.extern.slf4j.Slf4j;
21  import org.bremersee.exception.ServiceException;
22  import org.ldaptive.AddOperation;
23  import org.ldaptive.AddRequest;
24  import org.ldaptive.AttributeModification;
25  import org.ldaptive.BindOperation;
26  import org.ldaptive.BindRequest;
27  import org.ldaptive.BindResponse;
28  import org.ldaptive.CompareOperation;
29  import org.ldaptive.CompareRequest;
30  import org.ldaptive.CompareResponse;
31  import org.ldaptive.ConnectionFactory;
32  import org.ldaptive.DeleteOperation;
33  import org.ldaptive.DeleteRequest;
34  import org.ldaptive.LdapAttribute;
35  import org.ldaptive.LdapEntry;
36  import org.ldaptive.LdapException;
37  import org.ldaptive.ModifyDnOperation;
38  import org.ldaptive.ModifyDnRequest;
39  import org.ldaptive.ModifyOperation;
40  import org.ldaptive.ModifyRequest;
41  import org.ldaptive.Operation;
42  import org.ldaptive.Request;
43  import org.ldaptive.Result;
44  import org.ldaptive.ResultCode;
45  import org.ldaptive.SearchOperation;
46  import org.ldaptive.SearchRequest;
47  import org.ldaptive.SearchResponse;
48  import org.ldaptive.extended.ExtendedOperation;
49  import org.ldaptive.extended.ExtendedRequest;
50  import org.ldaptive.extended.ExtendedResponse;
51  import org.ldaptive.handler.ResultPredicate;
52  import org.springframework.util.Assert;
53  import org.springframework.util.ErrorHandler;
54  
55  /**
56   * The template for executing ldap operations.
57   *
58   * @author Christian Bremer
59   */
60  @Slf4j
61  @SuppressWarnings("WeakerAccess")
62  public class LdaptiveTemplate implements LdaptiveOperations, Cloneable {
63  
64    private static final ResultPredicate NOT_COMPARE_RESULT = result -> !result.isSuccess()
65        && result.getResultCode() != ResultCode.COMPARE_TRUE
66        && result.getResultCode() != ResultCode.COMPARE_FALSE;
67  
68    private static final ResultPredicate NOT_DELETE_RESULT = result -> !result.isSuccess()
69        && result.getResultCode() != ResultCode.NO_SUCH_OBJECT;
70  
71    private static final ResultPredicate NOT_FIND_RESULT = NOT_DELETE_RESULT;
72  
73    private final ConnectionFactory connectionFactory;
74  
75    private ErrorHandler errorHandler = new DefaultLdaptiveErrorHandler();
76  
77    /**
78     * Instantiates a new ldap template.
79     *
80     * @param connectionFactory the connection factory
81     */
82    public LdaptiveTemplate(ConnectionFactory connectionFactory) {
83      Assert.notNull(connectionFactory, "Connection factory must not be null.");
84      this.connectionFactory = connectionFactory;
85    }
86  
87    @Override
88    public ConnectionFactory getConnectionFactory() {
89      return connectionFactory;
90    }
91  
92    /**
93     * Sets error handler.
94     *
95     * @param errorHandler the error handler
96     */
97    public void setErrorHandler(final ErrorHandler errorHandler) {
98      if (errorHandler != null) {
99        this.errorHandler = errorHandler;
100     }
101   }
102 
103   /**
104    * Returns a new instance of this ldaptive template with the same connection factory and error handler.
105    *
106    * @return a new instance of this ldaptive template
107    */
108   @SuppressWarnings("MethodDoesntCallSuperMethod")
109   @Override
110   public LdaptiveTemplate clone() {
111     return clone(null);
112   }
113 
114   /**
115    * Returns a new instance of this ldaptive template with the same connection factory and the given error handler.
116    *
117    * @param errorHandler the new error handler
118    * @return the new instance of the ldaptive template
119    */
120   public LdaptiveTemplate clone(final ErrorHandler errorHandler) {
121     final LdaptiveTemplatelate.html#LdaptiveTemplate">LdaptiveTemplate template = new LdaptiveTemplate(connectionFactory);
122     template.setErrorHandler(errorHandler);
123     return template;
124   }
125 
126   private <Q extends Request, S extends Result> S execute(Operation<Q, S> operation, Q request) {
127     try {
128       return operation.execute(request);
129 
130     } catch (Exception e) {
131       errorHandler.handleError(e);
132       return null;
133     }
134   }
135 
136   @Override
137   public void add(AddRequest request) {
138     execute(
139         AddOperation.builder()
140             .factory(getConnectionFactory())
141             .throwIf(ResultPredicate.NOT_SUCCESS)
142             .build(),
143         request);
144   }
145 
146   @Override
147   public boolean bind(BindRequest request) {
148     return Optional.ofNullable(execute(new BindOperation(getConnectionFactory()), request))
149         .map(BindResponse::isSuccess)
150         .orElse(false);
151   }
152 
153   @Override
154   public boolean compare(CompareRequest request) {
155     return Optional.ofNullable(execute(
156             CompareOperation.builder()
157                 .factory(getConnectionFactory())
158                 .throwIf(NOT_COMPARE_RESULT)
159                 .build(),
160             request))
161         .map(CompareResponse::isTrue)
162         .orElse(false);
163   }
164 
165   @Override
166   public void delete(DeleteRequest request) {
167     execute(
168         DeleteOperation.builder()
169             .factory(getConnectionFactory())
170             .throwIf(NOT_DELETE_RESULT)
171             .build(),
172         request);
173   }
174 
175   @Override
176   public ExtendedResponse executeExtension(ExtendedRequest request) {
177     return execute(
178         ExtendedOperation.builder()
179             .factory(getConnectionFactory())
180             .throwIf(ResultPredicate.NOT_SUCCESS)
181             .build(),
182         request);
183   }
184 
185   @Override
186   public void modify(ModifyRequest request) {
187     if (request.getModifications() != null && request.getModifications().length > 0) {
188       execute(
189           ModifyOperation.builder()
190               .factory(getConnectionFactory())
191               .throwIf(ResultPredicate.NOT_SUCCESS)
192               .build(),
193           request);
194     }
195   }
196 
197   @Override
198   public void modifyDn(ModifyDnRequest request) {
199     execute(
200         ModifyDnOperation.builder()
201             .factory(getConnectionFactory())
202             .throwIf(ResultPredicate.NOT_SUCCESS)
203             .build(),
204         request);
205   }
206 
207   @Override
208   public SearchResponse search(SearchRequest request) {
209     return execute(
210         SearchOperation.builder()
211             .factory(getConnectionFactory())
212             .throwIf(NOT_FIND_RESULT)
213             .build(),
214         request);
215   }
216 
217   @Override
218   public boolean exists(String dn) {
219     try {
220       SearchResponse response = SearchOperation.builder()
221           .factory(getConnectionFactory())
222           .throwIf(NOT_FIND_RESULT)
223           .build()
224           .execute(SearchRequest.objectScopeSearchRequest(dn));
225       return response.isSuccess();
226 
227     } catch (LdapException e) {
228       errorHandler.handleError(e);
229       return false;
230     }
231   }
232 
233   @Override
234   public <T> T save(T domainObject, LdaptiveEntryMapper<T> entryMapper) {
235 
236     String dn = entryMapper.mapDn(domainObject);
237     SearchResponse searchResponse = execute(
238         SearchOperation.builder()
239             .factory(getConnectionFactory())
240             .throwIf(result -> result.getResultCode() != ResultCode.NO_SUCH_OBJECT
241                 && result.getResultCode() != ResultCode.SUCCESS)
242             .build(),
243         SearchRequest.objectScopeSearchRequest(dn));
244     return Optional.ofNullable(searchResponse)
245         .map(SearchResponse::getEntry)
246         .map(entry -> {
247           AttributeModification[] modifications = entryMapper.mapAndComputeModifications(domainObject, entry);
248           modify(new ModifyRequest(dn, modifications));
249           return entryMapper.map(entry);
250         })
251         .orElseGet(() -> {
252           String[] objectClasses = entryMapper.getObjectClasses();
253           if (objectClasses == null || objectClasses.length == 0) {
254             final ServiceException se = ServiceException.internalServerError(
255                 "Object classes must be specified to save a new ldap entry.",
256                 "org.bremersee:common-base-ldaptive:d7aa5699-fd2e-45df-a863-97960e8095b8");
257             log.error("Saving domain object failed.", se);
258             throw se;
259           }
260           LdapEntry entry = new LdapEntry();
261           entryMapper.map(domainObject, entry);
262           entry.setDn(dn);
263           entry.addAttributes(new LdapAttribute("objectclass", objectClasses));
264           add(new AddRequest(dn, entry.getAttributes()));
265           return entryMapper.map(entry);
266         });
267   }
268 
269 }