View Javadoc
1   /*
2   * Copyright 2019-2026 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.ldaptive;
18  
19  import java.util.Objects;
20  import java.util.Optional;
21  import org.apache.commons.logging.Log;
22  import org.apache.commons.logging.LogFactory;
23  import org.bremersee.exception.ServiceException;
24  import org.ldaptive.AddOperation;
25  import org.ldaptive.AddRequest;
26  import org.ldaptive.BindOperation;
27  import org.ldaptive.BindRequest;
28  import org.ldaptive.BindResponse;
29  import org.ldaptive.CompareOperation;
30  import org.ldaptive.CompareRequest;
31  import org.ldaptive.CompareResponse;
32  import org.ldaptive.ConnectionFactory;
33  import org.ldaptive.DeleteOperation;
34  import org.ldaptive.DeleteRequest;
35  import org.ldaptive.LdapAttribute;
36  import org.ldaptive.LdapEntry;
37  import org.ldaptive.LdapException;
38  import org.ldaptive.ModifyDnOperation;
39  import org.ldaptive.ModifyDnRequest;
40  import org.ldaptive.ModifyOperation;
41  import org.ldaptive.ModifyRequest;
42  import org.ldaptive.Operation;
43  import org.ldaptive.Request;
44  import org.ldaptive.Result;
45  import org.ldaptive.ResultCode;
46  import org.ldaptive.SearchOperation;
47  import org.ldaptive.SearchRequest;
48  import org.ldaptive.SearchResponse;
49  import org.ldaptive.extended.ExtendedOperation;
50  import org.ldaptive.extended.ExtendedRequest;
51  import org.ldaptive.extended.ExtendedResponse;
52  import org.ldaptive.handler.ResultPredicate;
53  
54  /**
55   * The template for executing ldap operations.
56   *
57   * @author Christian Bremer
58   */
59  public class LdaptiveTemplate implements LdaptiveOperations {
60  
61    private static final Log log = LogFactory.getLog(LdaptiveTemplate.class);
62  
63    private static final ResultPredicate NOT_COMPARE_RESULT = result -> !result.isSuccess()
64        && result.getResultCode() != ResultCode.COMPARE_TRUE
65        && result.getResultCode() != ResultCode.COMPARE_FALSE;
66  
67    private static final ResultPredicate NOT_DELETE_RESULT = result -> !result.isSuccess()
68        && result.getResultCode() != ResultCode.NO_SUCH_OBJECT;
69  
70    private static final ResultPredicate NOT_FIND_RESULT = NOT_DELETE_RESULT;
71  
72    private final ConnectionFactory connectionFactory;
73  
74    private LdaptiveErrorHandler errorHandler = new DefaultLdaptiveErrorHandler();
75  
76    /**
77     * Instantiates a new ldaptive template.
78     *
79     * @param connectionFactory the connection factory
80     */
81    public LdaptiveTemplate(ConnectionFactory connectionFactory) {
82      if (Objects.isNull(connectionFactory)) {
83        throw new IllegalArgumentException("Connection factory must not be present.");
84      }
85      this.connectionFactory = connectionFactory;
86    }
87  
88    @Override
89    public ConnectionFactory getConnectionFactory() {
90      return connectionFactory;
91    }
92  
93    /**
94     * Sets error handler.
95     *
96     * @param errorHandler the error handler
97     */
98    public void setErrorHandler(final LdaptiveErrorHandler errorHandler) {
99      if (errorHandler != null) {
100       this.errorHandler = errorHandler;
101     }
102   }
103 
104   @Override
105   public LdaptiveTemplate copy() {
106     return copy(null);
107   }
108 
109   @Override
110   public LdaptiveTemplate copy(LdaptiveErrorHandler errorHandler) {
111     final LdaptiveTemplate template = new LdaptiveTemplate(connectionFactory);
112     template.setErrorHandler(errorHandler);
113     return template;
114   }
115 
116   private <Q extends Request, S extends Result> S execute(Operation<Q, S> operation, Q request) {
117     try {
118       return operation.execute(request);
119 
120     } catch (Exception e) {
121       errorHandler.handleError(e);
122       return null;
123     }
124   }
125 
126   @Override
127   public void add(AddRequest request) {
128     execute(
129         AddOperation.builder()
130             .factory(getConnectionFactory())
131             .throwIf(ResultPredicate.NOT_SUCCESS)
132             .build(),
133         request);
134   }
135 
136   @Override
137   public boolean bind(BindRequest request) {
138     return Optional.ofNullable(execute(new BindOperation(getConnectionFactory()), request))
139         .map(BindResponse::isSuccess)
140         .orElse(false);
141   }
142 
143   @Override
144   public boolean compare(CompareRequest request) {
145     return Optional.ofNullable(execute(
146             CompareOperation.builder()
147                 .factory(getConnectionFactory())
148                 .throwIf(NOT_COMPARE_RESULT)
149                 .build(),
150             request))
151         .map(CompareResponse::isTrue)
152         .orElse(false);
153   }
154 
155   @Override
156   public void delete(DeleteRequest request) {
157     execute(
158         DeleteOperation.builder()
159             .factory(getConnectionFactory())
160             .throwIf(NOT_DELETE_RESULT)
161             .build(),
162         request);
163   }
164 
165   @Override
166   public ExtendedResponse executeExtension(ExtendedRequest request) {
167     return execute(
168         ExtendedOperation.builder()
169             .factory(getConnectionFactory())
170             .throwIf(ResultPredicate.NOT_SUCCESS)
171             .build(),
172         request);
173   }
174 
175   @Override
176   public void modify(ModifyRequest request) {
177     if (request.getModifications() != null && request.getModifications().length > 0) {
178       execute(
179           ModifyOperation.builder()
180               .factory(getConnectionFactory())
181               .throwIf(ResultPredicate.NOT_SUCCESS)
182               .build(),
183           request);
184     }
185   }
186 
187   @Override
188   public void modifyDn(ModifyDnRequest request) {
189     execute(
190         ModifyDnOperation.builder()
191             .factory(getConnectionFactory())
192             .throwIf(ResultPredicate.NOT_SUCCESS)
193             .build(),
194         request);
195   }
196 
197   @Override
198   public SearchResponse search(SearchRequest request) {
199     return execute(
200         SearchOperation.builder()
201             .factory(getConnectionFactory())
202             .throwIf(NOT_FIND_RESULT)
203             .build(),
204         request);
205   }
206 
207   @Override
208   public boolean exists(String dn) {
209     try {
210       SearchResponse response = SearchOperation.builder()
211           .factory(getConnectionFactory())
212           .throwIf(NOT_FIND_RESULT)
213           .build()
214           .execute(SearchRequest.objectScopeSearchRequest(dn));
215       return response.isSuccess();
216 
217     } catch (LdapException e) {
218       errorHandler.handleError(e);
219       return false;
220     }
221   }
222 
223   @Override
224   public <T> T save(T domainObject, LdaptiveEntryMapper<T> entryMapper) {
225 
226     String dn = entryMapper.mapDn(domainObject);
227     SearchResponse searchResponse = execute(
228         SearchOperation.builder()
229             .factory(getConnectionFactory())
230             .throwIf(result -> result.getResultCode() != ResultCode.NO_SUCH_OBJECT
231                 && result.getResultCode() != ResultCode.SUCCESS)
232             .build(),
233         SearchRequest.objectScopeSearchRequest(dn));
234     return Optional.ofNullable(searchResponse)
235         .map(SearchResponse::getEntry)
236         .map(entry -> entryMapper.mapAndComputeModifyRequest(domainObject, entry)
237             .map(modReq -> {
238               modify(modReq);
239               return entryMapper.map(entry);
240             })
241             .orElse(domainObject))  // domain object exists, but nothing to modify
242         .orElseGet(() -> {          // otherwise, save new ldap entry
243           String[] objectClasses = entryMapper.getObjectClasses();
244           if (objectClasses == null || objectClasses.length == 0) {
245             final ServiceException se = ServiceException.internalServerError(
246                 "Object classes must be specified to save a new ldap entry.",
247                 "org.bremersee:ldaptive-integration:d7aa5699-fd2e-45df-a863-97960e8095b8");
248             log.error("Saving domain object failed.", se);
249             throw se;
250           }
251           LdapEntry entry = new LdapEntry();
252           entryMapper.map(domainObject, entry);
253           entry.setDn(dn);
254           entry.addAttributes(new LdapAttribute("objectclass", objectClasses));
255           add(new AddRequest(dn, entry.getAttributes()));
256           return entryMapper.map(entry);
257         });
258   }
259 
260 }