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 2006-2009 Sun Microsystems, Inc.
015 * Portions Copyright 2012-2016 ForgeRock AS.
016 */
017package org.opends.dsml.protocol;
018
019import java.io.IOException;
020import java.util.ArrayList;
021import java.util.LinkedHashSet;
022import java.util.LinkedList;
023import java.util.List;
024
025import javax.xml.bind.JAXBElement;
026
027import org.forgerock.i18n.LocalizableMessage;
028import org.forgerock.opendj.ldap.DecodeException;
029import org.opends.server.protocols.ldap.LDAPAttribute;
030import org.opends.server.protocols.ldap.LDAPConstants;
031import org.opends.server.protocols.ldap.LDAPFilter;
032import org.opends.server.protocols.ldap.LDAPMessage;
033import org.opends.server.protocols.ldap.LDAPResultCode;
034import org.opends.server.protocols.ldap.SearchRequestProtocolOp;
035import org.opends.server.protocols.ldap.SearchResultDoneProtocolOp;
036import org.opends.server.protocols.ldap.SearchResultEntryProtocolOp;
037import org.opends.server.tools.LDAPConnection;
038import org.forgerock.opendj.ldap.ByteString;
039import org.forgerock.opendj.ldap.DereferenceAliasesPolicy;
040import org.opends.server.types.LDAPException;
041import org.opends.server.types.RawFilter;
042import org.forgerock.opendj.ldap.SearchScope;
043import static org.opends.messages.ProtocolMessages.*;
044
045/**
046 * This class provides the functionality for the performing an LDAP
047 * SEARCH operation based on the specified DSML request.
048 */
049public class DSMLSearchOperation
050{
051
052  private LDAPConnection connection;
053
054
055
056  /**
057   * Create the instance with the specified connection.
058   *
059   * @param connection
060   *          The LDAP connection to send the request on.
061   */
062
063  public DSMLSearchOperation(LDAPConnection connection)
064  {
065    this.connection = connection;
066  }
067
068
069
070  /**
071   * Returns a new AND search filter with the provided filter
072   * components.
073   *
074   * @param filterSet
075   *          The filter components for this filter
076   * @return a new AND search filter with the provided filter
077   *         components.
078   * @throws LDAPException
079   *           an LDAPException is thrown if the creation of a filter
080   *           component fails.
081   * @throws IOException if a value is an anyURI and cannot be fetched.
082   */
083  private static LDAPFilter createANDFilter(FilterSet filterSet)
084      throws LDAPException, IOException
085  {
086    List<JAXBElement<?>> list = filterSet.getFilterGroup();
087    ArrayList<RawFilter> filters = new ArrayList<>(list.size());
088
089    for (JAXBElement<?> filter : list)
090    {
091      filters.add(createFilter(filter));
092    }
093    return LDAPFilter.createANDFilter(filters);
094  }
095
096
097
098  /**
099   * Returns a new Approximate search filter with the provided
100   * information.
101   *
102   * @param ava
103   *          the attribute value assertion for this approximate
104   *          filter.
105   * @return a new Approximate search filter with the provided
106   *         information.
107   * @throws IOException if a value is an anyURI and cannot be fetched.
108   */
109  private static LDAPFilter createApproximateFilter(AttributeValueAssertion ava)
110    throws IOException
111  {
112    return LDAPFilter.createApproximateFilter(ava.getName(),
113        ByteStringUtility.convertValue(ava.getValue()));
114  }
115
116
117
118  /**
119   * Returns a new Equality search filter with the provided
120   * information.
121   *
122   * @param ava
123   *          the attribute value assertion for this Equality filter.
124   * @return a new Equality search filter with the provided
125   *         information.
126   * @throws IOException if a value is an anyURI and cannot be fetched.
127   */
128  private static LDAPFilter createEqualityFilter(AttributeValueAssertion ava)
129    throws IOException
130  {
131    return LDAPFilter.createEqualityFilter(ava.getName(),
132        ByteStringUtility.convertValue(ava.getValue()));
133  }
134
135
136
137  /**
138   * Returns a new Extensible search filter with the provided
139   * information.
140   *
141   * @param mra
142   *          the matching rule assertion for this Extensible filter.
143   * @return a new Extensible search filter with the provided
144   *         information.
145   * @throws IOException if a value is an anyURI and cannot be fetched.
146   */
147  private static LDAPFilter createExtensibleFilter(MatchingRuleAssertion mra)
148    throws IOException
149  {
150    return LDAPFilter.createExtensibleFilter(mra.getMatchingRule(), mra
151        .getName(), ByteStringUtility.convertValue(mra.getValue()),
152        mra.isDnAttributes());
153  }
154
155
156
157  /**
158   * Returns a new GreaterOrEqual search filter with the provided
159   * information.
160   *
161   * @param ava
162   *          the attribute value assertion for this GreaterOrEqual
163   *          filter.
164   * @return a new GreaterOrEqual search filter with the provided
165   *         information.
166   * @throws IOException if a value is an anyURI and cannot be fetched.
167   */
168  private static LDAPFilter createGreaterOrEqualFilter(
169      AttributeValueAssertion ava)
170    throws IOException
171  {
172    return LDAPFilter.createGreaterOrEqualFilter(ava.getName(),
173        ByteStringUtility.convertValue(ava.getValue()));
174  }
175
176
177
178  /**
179   * Returns a new LessOrEqual search filter with the provided
180   * information.
181   *
182   * @param ava
183   *          the attribute value assertion for this LessOrEqual
184   *          filter.
185   * @return a new LessOrEqual search filter with the provided
186   *         information.
187   * @throws IOException if a value is an anyURI and cannot be fetched.
188   */
189  private static LDAPFilter createLessOrEqualFilter(AttributeValueAssertion ava)
190    throws IOException
191  {
192    return LDAPFilter.createLessOrEqualFilter(ava.getName(),
193        ByteStringUtility.convertValue(ava.getValue()));
194  }
195
196
197
198  /**
199   * Returns a new NOT search filter with the provided information.
200   *
201   * @param filter
202   *          the filter for this NOT filter.
203   * @return a new NOT search filter with the provided information.
204   * @throws LDAPException
205   *           an LDAPException is thrown if the creation of the
206   *           provided filter fails.
207   * @throws IOException if a value is an anyURI and cannot be fetched.
208   */
209  private static LDAPFilter createNOTFilter(Filter filter)
210    throws LDAPException, IOException
211  {
212    return LDAPFilter.createNOTFilter(createFilter(filter));
213  }
214
215
216
217  /**
218   * Returns a new OR search filter with the provided filter
219   * components.
220   *
221   * @param filterSet
222   *          The filter components for this filter
223   * @return a new OR search filter with the provided filter
224   *         components.
225   * @throws LDAPException
226   *           an LDAPException is thrown if the creation of a filter
227   *           component fails.
228   * @throws IOException if a value is an anyURI and cannot be fetched.
229   */
230  private static LDAPFilter createORFilter(FilterSet filterSet)
231      throws LDAPException, IOException
232  {
233    List<JAXBElement<?>> list = filterSet.getFilterGroup();
234    ArrayList<RawFilter> filters = new ArrayList<>(list.size());
235
236    for (JAXBElement<?> filter : list)
237    {
238      filters.add(createFilter(filter));
239    }
240    return LDAPFilter.createORFilter(filters);
241  }
242
243
244
245  /**
246   * Returns a new Present search filter with the provided
247   * information.
248   *
249   * @param ad
250   *          the attribute description for this Present filter.
251   * @return a new Present search filter with the provided information.
252   * @throws LDAPException
253   *           an LDAPException is thrown if the ASN.1 element
254   *           provided by the attribute description cannot be decoded
255   *           as a raw search filter.
256   */
257  private static LDAPFilter createPresentFilter(AttributeDescription ad)
258      throws LDAPException
259  {
260    return LDAPFilter.decode(ad.getName() + "=*");
261  }
262
263
264
265  /**
266   * Returns a new Substring search filter with the provided
267   * information.
268   *
269   * @param sf
270   *          the substring filter for this Substring filter.
271   * @return a new Substring search filter with the provided
272   *         information.
273   * @throws LDAPException if the filter could not be decoded.
274   * @throws IOException if a value is an anyURI and cannot be fetched.
275   */
276  private static LDAPFilter createSubstringFilter(SubstringFilter sf)
277        throws LDAPException, IOException
278  {
279    List<Object> anyo = sf.getAny();
280    ArrayList<ByteString> subAnyElements = new ArrayList<>(anyo.size());
281
282    for (Object o : anyo)
283    {
284      subAnyElements.add(ByteStringUtility.convertValue(o));
285    }
286    if(sf.getInitial() == null && subAnyElements.isEmpty()
287            && sf.getFinal()==null)
288    {
289      LocalizableMessage message = ERR_LDAP_FILTER_DECODE_NULL.get();
290      throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
291    }
292    return LDAPFilter.createSubstringFilter(sf.getName(),
293        sf.getInitial() == null ? null : ByteStringUtility
294            .convertValue(sf.getInitial()),
295        subAnyElements,
296        sf.getFinal() == null ? null : ByteStringUtility
297            .convertValue(sf.getFinal()));
298  }
299
300
301
302  /**
303   * Returns a new LDAPFilter according to the tag name of the
304   * provided element that can be "and", "or", "not", "equalityMatch",
305   * "substrings", "greaterOrEqual", "lessOrEqual", "present",
306   * "approxMatch", "extensibleMatch".
307   *
308   * @param xmlElement
309   *          a JAXBElement that contains the name of the filter to
310   *          create and the associated argument.
311   * @return a new LDAPFilter according to the tag name of the
312   *         provided element.
313   * @throws LDAPException
314   *           an LDAPException is thrown if the creation of the
315   *           targeted filter fails.
316   * @throws IOException if a value is an anyURI and cannot be fetched.
317   */
318  private static LDAPFilter createFilter(JAXBElement<?> xmlElement)
319      throws LDAPException, IOException
320  {
321    String filterName = xmlElement.getName().getLocalPart();
322    switch (filterName)
323    {
324    case "and":
325      // <xsd:element name="and" type="FilterSet"/>
326      return createANDFilter((FilterSet) xmlElement.getValue());
327
328    case "or":
329      // <xsd:element name="or" type="FilterSet"/>
330      return createORFilter((FilterSet) xmlElement.getValue());
331
332    case "not":
333      // <xsd:element name="not" type="Filter"/>
334      return createNOTFilter((Filter) xmlElement.getValue());
335
336    case "equalityMatch":
337      // <xsd:element name="equalityMatch"
338      // type="AttributeValueAssertion"/>
339      return createEqualityFilter((AttributeValueAssertion) xmlElement
340          .getValue());
341
342    case "substrings":
343      // <xsd:element name="substrings" type="SubstringFilter"/>
344      return createSubstringFilter((SubstringFilter) xmlElement.getValue());
345
346    case "greaterOrEqual":
347      // <xsd:element name="greaterOrEqual"
348      // type="AttributeValueAssertion"/>
349      return createGreaterOrEqualFilter((AttributeValueAssertion) xmlElement
350          .getValue());
351
352    case "lessOrEqual":
353      // <xsd:element name="lessOrEqual"
354      // type="AttributeValueAssertion"/>
355      return createLessOrEqualFilter((AttributeValueAssertion) xmlElement
356          .getValue());
357
358    case "present":
359      // <xsd:element name="present" type="AttributeDescription"/>
360      return createPresentFilter((AttributeDescription) xmlElement.getValue());
361
362    case "approxMatch":
363      // <xsd:element name="approxMatch"
364      // type="AttributeValueAssertion"/>
365      return createApproximateFilter((AttributeValueAssertion) xmlElement
366          .getValue());
367
368    case "extensibleMatch":
369      // <xsd:element name="extensibleMatch"
370      // type="MatchingRuleAssertion"/>
371      return createExtensibleFilter((MatchingRuleAssertion) xmlElement
372          .getValue());
373
374    default:
375      return null;
376    }
377  }
378
379
380
381  /**
382   * Returns a new LDAPFilter according to the filter assigned to the
383   * provided filter.
384   *
385   * @param filter
386   *          a filter that contains the object filter to create.
387   * @return a new LDAPFilter according to the filter assigned to the
388   *         provided filter.
389   * @throws LDAPException
390   *           an LDAPException is thrown if the creation of the
391   *           targeted filter fails.
392   * @throws IOException if a value is an anyURI and cannot be fetched.
393   */
394  private static LDAPFilter createFilter(Filter filter)
395    throws LDAPException, IOException
396  {
397    if (filter.getAnd() != null)
398    {
399      return createANDFilter(filter.getAnd());
400    }
401    else if (filter.getApproxMatch() != null)
402    {
403      return createApproximateFilter(filter.getApproxMatch());
404    }
405    else if (filter.getEqualityMatch() != null)
406    {
407      return createEqualityFilter(filter.getEqualityMatch());
408    }
409    else if (filter.getExtensibleMatch() != null)
410    {
411      return createExtensibleFilter(filter.getExtensibleMatch());
412    }
413    else if (filter.getGreaterOrEqual() != null)
414    {
415      return createGreaterOrEqualFilter(filter.getGreaterOrEqual());
416    }
417    else if (filter.getLessOrEqual() != null)
418    {
419      return createLessOrEqualFilter(filter.getLessOrEqual());
420    }
421    else if (filter.getNot() != null)
422    {
423      return createNOTFilter(filter.getNot());
424    }
425    else if (filter.getOr() != null)
426    {
427      return createORFilter(filter.getOr());
428    }
429    else if (filter.getPresent() != null)
430    {
431      return createPresentFilter(filter.getPresent());
432    }
433    else if (filter.getSubstrings() != null)
434    {
435      return createSubstringFilter(filter.getSubstrings());
436    }
437    return null;
438  }
439
440
441
442  /**
443   * Perform the LDAP SEARCH operation and send the result back to the
444   * client.
445   *
446   * @param objFactory
447   *          The object factory for this operation.
448   * @param searchRequest
449   *          The search request for this operation.
450   * @param controls
451   *          Any required controls (e.g. for proxy authz).
452   * @return The result of the search operation.
453   * @throws IOException
454   *           If an I/O problem occurs.
455   * @throws LDAPException
456   *           If an error occurs while interacting with an LDAP
457   *           element.
458   */
459  public SearchResponse doSearch(ObjectFactory objFactory,
460      SearchRequest searchRequest,
461      List<org.opends.server.types.Control> controls)
462  throws IOException, LDAPException
463  {
464    SearchResponse searchResponse = objFactory.createSearchResponse();
465    searchResponse.setRequestID(searchRequest.getRequestID());
466
467    LDAPFilter filter = createFilter(searchRequest.getFilter());
468
469    DereferenceAliasesPolicy derefPolicy = DereferenceAliasesPolicy.NEVER;
470    String derefStr = searchRequest.getDerefAliases().toLowerCase();
471    if (derefStr.equals("derefinsearching"))
472    {
473      derefPolicy = DereferenceAliasesPolicy.IN_SEARCHING;
474    }
475    else if (derefStr.equals("dereffindingbaseobj"))
476    {
477      derefPolicy = DereferenceAliasesPolicy.FINDING_BASE;
478    }
479    else if (derefStr.equals("derefalways"))
480    {
481      derefPolicy = DereferenceAliasesPolicy.ALWAYS;
482    }
483
484    SearchScope scope = SearchScope.WHOLE_SUBTREE;
485    String scopeStr = searchRequest.getScope().toLowerCase();
486    if (scopeStr.equals("singlelevel") || scopeStr.equals("one"))
487    {
488      scope = SearchScope.SINGLE_LEVEL;
489    }
490    else if (scopeStr.equals("baseobject") || scopeStr.equals("base"))
491    {
492      scope = SearchScope.BASE_OBJECT;
493    }
494
495    LinkedHashSet<String> attributes = new LinkedHashSet<>();
496    // Get the list of attributes.
497    AttributeDescriptions attrDescriptions = searchRequest.getAttributes();
498    if (attrDescriptions != null)
499    {
500      List<AttributeDescription> attrDesc = attrDescriptions.getAttribute();
501      for (AttributeDescription desc : attrDesc)
502      {
503        attributes.add(desc.getName());
504      }
505    }
506
507    SearchRequestProtocolOp protocolOp = new SearchRequestProtocolOp(ByteString
508        .valueOfUtf8(searchRequest.getDn()), scope, derefPolicy,
509        (int) searchRequest.getSizeLimit(), (int) searchRequest.getTimeLimit(),
510        searchRequest.isTypesOnly(), filter, attributes);
511    try
512    {
513      LDAPMessage msg =
514        new LDAPMessage(DSMLServlet.nextMessageID(), protocolOp, controls);
515      connection.getLDAPWriter().writeMessage(msg);
516
517      byte opType;
518      do
519      {
520        int resultCode = 0;
521        LocalizableMessage errorMessage = null;
522        LDAPMessage responseMessage = connection.getLDAPReader().readMessage();
523        if(responseMessage == null)
524        {
525          //The server disconnected silently. At this point we don't know if it
526          // is a protocol error or anything else. Since we didn't hear from
527          // the server , we have a reason to believe that the server doesn't
528          // want to handle this request. Let us return unavailable error
529          // code to the client to cover possible cases.
530          LocalizableMessage message = ERR_UNEXPECTED_CONNECTION_CLOSURE.get();
531          LDAPResult result = objFactory.createLDAPResult();
532          ResultCode code = ResultCodeFactory.create(objFactory,
533              LDAPResultCode.UNAVAILABLE);
534          result.setResultCode(code);
535          result.setErrorMessage(message.toString());
536          searchResponse.setSearchResultDone(result);
537          return searchResponse;
538        }
539        opType = responseMessage.getProtocolOpType();
540        switch (opType)
541        {
542        case LDAPConstants.OP_TYPE_SEARCH_RESULT_ENTRY:
543          SearchResultEntryProtocolOp searchEntryOp = responseMessage
544              .getSearchResultEntryProtocolOp();
545
546          SearchResultEntry entry = objFactory.createSearchResultEntry();
547          java.util.List<DsmlAttr> attrList = entry.getAttr();
548
549          LinkedList<LDAPAttribute> attrs = searchEntryOp.getAttributes();
550
551          for (LDAPAttribute attr : attrs)
552          {
553            String nm = attr.getAttributeType();
554            DsmlAttr dsmlAttr = objFactory.createDsmlAttr();
555
556            dsmlAttr.setName(nm);
557            List<Object> dsmlAttrVal = dsmlAttr.getValue();
558            List<ByteString> vals = attr.getValues();
559            for (ByteString val : vals)
560            {
561              dsmlAttrVal.add(ByteStringUtility.convertByteString(val));
562            }
563            attrList.add(dsmlAttr);
564          }
565
566          entry.setDn(searchEntryOp.getDN().toString());
567          searchResponse.getSearchResultEntry().add(entry);
568          break;
569
570        case LDAPConstants.OP_TYPE_SEARCH_RESULT_REFERENCE:
571          responseMessage.getSearchResultReferenceProtocolOp();
572          break;
573
574        case LDAPConstants.OP_TYPE_SEARCH_RESULT_DONE:
575          SearchResultDoneProtocolOp searchOp = responseMessage
576              .getSearchResultDoneProtocolOp();
577          resultCode = searchOp.getResultCode();
578          errorMessage = searchOp.getErrorMessage();
579          LDAPResult result = objFactory.createLDAPResult();
580          ResultCode code = ResultCodeFactory.create(objFactory, resultCode);
581          result.setResultCode(code);
582          result.setErrorMessage(errorMessage != null ? errorMessage.toString()
583              : null);
584          if (searchOp.getMatchedDN() != null)
585          {
586            result.setMatchedDN(searchOp.getMatchedDN().toString());
587          }
588          searchResponse.setSearchResultDone(result);
589          break;
590        default:
591          throw new RuntimeException("Invalid protocol operation:" + opType);
592        }
593      }
594      while (opType != LDAPConstants.OP_TYPE_SEARCH_RESULT_DONE);
595
596    }
597    catch (DecodeException ae)
598    {
599      ae.printStackTrace();
600      throw new IOException(ae.getMessage());
601    }
602
603    return searchResponse;
604  }
605}