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