/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
* add the following below this CDDL HEADER, with the fields enclosed
* by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2006-2009 Sun Microsystems, Inc.
* Portions Copyright 2012 ForgeRock AS.
*/
package org.opends.dsml.protocol;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import javax.xml.bind.JAXBElement;
import org.opends.messages.Message;
import org.opends.server.protocols.asn1.ASN1Exception;
import org.opends.server.protocols.ldap.LDAPAttribute;
import org.opends.server.protocols.ldap.LDAPConstants;
import org.opends.server.protocols.ldap.LDAPFilter;
import org.opends.server.protocols.ldap.LDAPMessage;
import org.opends.server.protocols.ldap.LDAPResultCode;
import org.opends.server.protocols.ldap.SearchRequestProtocolOp;
import org.opends.server.protocols.ldap.SearchResultDoneProtocolOp;
import org.opends.server.protocols.ldap.SearchResultEntryProtocolOp;
import org.opends.server.tools.LDAPConnection;
import org.opends.server.types.ByteString;
import org.opends.server.types.DereferencePolicy;
import org.opends.server.types.LDAPException;
import org.opends.server.types.RawFilter;
import org.opends.server.types.SearchScope;
import static org.opends.messages.ProtocolMessages.*;
/**
* This class provides the functionality for the performing an LDAP
* SEARCH operation based on the specified DSML request.
*/
public class DSMLSearchOperation
{
private LDAPConnection connection;
/**
* Create the instance with the specified connection.
*
* @param connection
* The LDAP connection to send the request on.
*/
public DSMLSearchOperation(LDAPConnection connection)
{
this.connection = connection;
}
/**
* Returns a new AND search filter with the provided filter
* components.
*
* @param filterSet
* The filter components for this filter
* @return a new AND search filter with the provided filter
* components.
* @throws LDAPException
* an LDAPException is thrown if the creation of a filter
* component fails.
* @throws IOException if a value is an anyURI and cannot be fetched.
*/
private static LDAPFilter createANDFilter(FilterSet filterSet)
throws LDAPException, IOException
{
List<JAXBElement<?>> list = filterSet.getFilterGroup();
ArrayList<RawFilter> filters = new ArrayList<RawFilter>(list.size());
for (JAXBElement<?> filter : list)
{
filters.add(createFilter(filter));
}
return LDAPFilter.createANDFilter(filters);
}
/**
* Returns a new Approximate search filter with the provided
* information.
*
* @param ava
* the attribute value assertion for this approximate
* filter.
* @return a new Approximate search filter with the provided
* information.
* @throws IOException if a value is an anyURI and cannot be fetched.
*/
private static LDAPFilter createApproximateFilter(AttributeValueAssertion ava)
throws IOException
{
return LDAPFilter.createApproximateFilter(ava.getName(),
ByteStringUtility.convertValue(ava.getValue()));
}
/**
* Returns a new Equality search filter with the provided
* information.
*
* @param ava
* the attribute value assertion for this Equality filter.
* @return a new Equality search filter with the provided
* information.
* @throws IOException if a value is an anyURI and cannot be fetched.
*/
private static LDAPFilter createEqualityFilter(AttributeValueAssertion ava)
throws IOException
{
return LDAPFilter.createEqualityFilter(ava.getName(),
ByteStringUtility.convertValue(ava.getValue()));
}
/**
* Returns a new Extensible search filter with the provided
* information.
*
* @param mra
* the matching rule assertion for this Extensible filter.
* @return a new Extensible search filter with the provided
* information.
* @throws IOException if a value is an anyURI and cannot be fetched.
*/
private static LDAPFilter createExtensibleFilter(MatchingRuleAssertion mra)
throws IOException
{
return LDAPFilter.createExtensibleFilter(mra.getMatchingRule(), mra
.getName(), ByteStringUtility.convertValue(mra.getValue()),
mra.isDnAttributes());
}
/**
* Returns a new GreaterOrEqual search filter with the provided
* information.
*
* @param ava
* the attribute value assertion for this GreaterOrEqual
* filter.
* @return a new GreaterOrEqual search filter with the provided
* information.
* @throws IOException if a value is an anyURI and cannot be fetched.
*/
private static LDAPFilter createGreaterOrEqualFilter(
AttributeValueAssertion ava)
throws IOException
{
return LDAPFilter.createGreaterOrEqualFilter(ava.getName(),
ByteStringUtility.convertValue(ava.getValue()));
}
/**
* Returns a new LessOrEqual search filter with the provided
* information.
*
* @param ava
* the attribute value assertion for this LessOrEqual
* filter.
* @return a new LessOrEqual search filter with the provided
* information.
* @throws IOException if a value is an anyURI and cannot be fetched.
*/
private static LDAPFilter createLessOrEqualFilter(AttributeValueAssertion ava)
throws IOException
{
return LDAPFilter.createLessOrEqualFilter(ava.getName(),
ByteStringUtility.convertValue(ava.getValue()));
}
/**
* Returns a new NOT search filter with the provided information.
*
* @param filter
* the filter for this NOT filter.
* @return a new NOT search filter with the provided information.
* @throws LDAPException
* an LDAPException is thrown if the creation of the
* provided filter fails.
* @throws IOException if a value is an anyURI and cannot be fetched.
*/
private static LDAPFilter createNOTFilter(Filter filter)
throws LDAPException, IOException
{
return LDAPFilter.createNOTFilter(createFilter(filter));
}
/**
* Returns a new OR search filter with the provided filter
* components.
*
* @param filterSet
* The filter components for this filter
* @return a new OR search filter with the provided filter
* components.
* @throws LDAPException
* an LDAPException is thrown if the creation of a filter
* component fails.
* @throws IOException if a value is an anyURI and cannot be fetched.
*/
private static LDAPFilter createORFilter(FilterSet filterSet)
throws LDAPException, IOException
{
List<JAXBElement<?>> list = filterSet.getFilterGroup();
ArrayList<RawFilter> filters = new ArrayList<RawFilter>(list.size());
for (JAXBElement<?> filter : list)
{
filters.add(createFilter(filter));
}
return LDAPFilter.createORFilter(filters);
}
/**
* Returns a new Present search filter with the provided
* information.
*
* @param ad
* the attribute description for this Present filter.
* @returna new Present search filter with the provided information.
* @throws LDAPException
* an LDAPException is thrown if the ASN.1 element
* provided by the attribute description cannot be decoded
* as a raw search filter.
*/
private static LDAPFilter createPresentFilter(AttributeDescription ad)
throws LDAPException
{
return LDAPFilter.decode(new StringBuilder(ad.getName()).append("=*")
.toString());
}
/**
* Returns a new Substring search filter with the provided
* information.
*
* @param sf
* the substring filter for this Substring filter.
* @return a new Substring search filter with the provided
* information.
* @throws LDAPException if the filter could not be decoded.
* @throws IOException if a value is an anyURI and cannot be fetched.
*/
private static LDAPFilter createSubstringFilter(SubstringFilter sf)
throws LDAPException, IOException
{
List<Object> anyo = sf.getAny();
ArrayList<ByteString> subAnyElements = new ArrayList<ByteString>(anyo
.size());
for (Object o : anyo)
{
subAnyElements.add(ByteStringUtility.convertValue(o));
}
if(sf.getInitial() == null && subAnyElements.isEmpty()
&& sf.getFinal()==null)
{
Message message = ERR_LDAP_FILTER_DECODE_NULL.get();
throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
}
return LDAPFilter.createSubstringFilter(sf.getName(),
sf.getInitial() == null ? null : ByteStringUtility
.convertValue(sf.getInitial()),
subAnyElements,
sf.getFinal() == null ? null : ByteStringUtility
.convertValue(sf.getFinal()));
}
/**
* Returns a new LDAPFilter according to the tag name of the
* provided element that can be "and", "or", "not", "equalityMatch",
* "substrings", "greaterOrEqual", "lessOrEqual", "present",
* "approxMatch", "extensibleMatch".
*
* @param xmlElement
* a JAXBElement that contains the name of the filter to
* create and the associated argument.
* @return a new LDAPFilter according to the tag name of the
* provided element.
* @throws LDAPException
* an LDAPException is thrown if the creation of the
* targeted filter fails.
* @throws IOException if a value is an anyURI and cannot be fetched.
*/
private static LDAPFilter createFilter(JAXBElement<?> xmlElement)
throws LDAPException, IOException
{
LDAPFilter result = null;
String filterName = xmlElement.getName().getLocalPart();
if ("and".equals(filterName))
{
// <xsd:element name="and" type="FilterSet"/>
result = createANDFilter((FilterSet) xmlElement.getValue());
}
else if ("or".equals(filterName))
{
// <xsd:element name="or" type="FilterSet"/>
result = createORFilter((FilterSet) xmlElement.getValue());
}
else if ("not".equals(filterName))
{
// <xsd:element name="not" type="Filter"/>
result = createNOTFilter((Filter) xmlElement.getValue());
}
else if ("equalityMatch".equals(filterName))
{
// <xsd:element name="equalityMatch"
// type="AttributeValueAssertion"/>
result = createEqualityFilter((AttributeValueAssertion) xmlElement
.getValue());
}
else if ("substrings".equals(filterName))
{
// <xsd:element name="substrings" type="SubstringFilter"/>
result = createSubstringFilter((SubstringFilter) xmlElement.getValue());
}
else if ("greaterOrEqual".equals(filterName))
{
// <xsd:element name="greaterOrEqual"
// type="AttributeValueAssertion"/>
result = createGreaterOrEqualFilter((AttributeValueAssertion) xmlElement
.getValue());
}
else if ("lessOrEqual".equals(filterName))
{
// <xsd:element name="lessOrEqual"
// type="AttributeValueAssertion"/>
result = createLessOrEqualFilter((AttributeValueAssertion) xmlElement
.getValue());
}
else if ("present".equals(filterName))
{
// <xsd:element name="present" type="AttributeDescription"/>
result =
createPresentFilter((AttributeDescription) xmlElement.getValue());
}
else if ("approxMatch".equals(filterName))
{
// <xsd:element name="approxMatch"
// type="AttributeValueAssertion"/>
result = createApproximateFilter((AttributeValueAssertion) xmlElement
.getValue());
}
else if ("extensibleMatch".equals(filterName))
{
// <xsd:element name="extensibleMatch"
// type="MatchingRuleAssertion"/>
result = createExtensibleFilter((MatchingRuleAssertion) xmlElement
.getValue());
}
return result;
}
/**
* Returns a new LDAPFilter according to the filter assigned to the
* provided filter.
*
* @param filter
* a filter that contains the object filter to create.
* @return a new LDAPFilter according to the filter assigned to the
* provided filter.
* @throws LDAPException
* an LDAPException is thrown if the creation of the
* targeted filter fails.
* @throws IOException if a value is an anyURI and cannot be fetched.
*/
private static LDAPFilter createFilter(Filter filter)
throws LDAPException, IOException
{
LDAPFilter result = null;
if (filter.getAnd() != null)
{
result = createANDFilter(filter.getAnd());
}
else if (filter.getApproxMatch() != null)
{
result = createApproximateFilter(filter.getApproxMatch());
}
else if (filter.getEqualityMatch() != null)
{
result = createEqualityFilter(filter.getEqualityMatch());
}
else if (filter.getExtensibleMatch() != null)
{
result = createExtensibleFilter(filter.getExtensibleMatch());
}
else if (filter.getGreaterOrEqual() != null)
{
result = createGreaterOrEqualFilter(filter.getGreaterOrEqual());
}
else if (filter.getLessOrEqual() != null)
{
result = createLessOrEqualFilter(filter.getLessOrEqual());
}
else if (filter.getNot() != null)
{
result = createNOTFilter(filter.getNot());
}
else if (filter.getOr() != null)
{
result = createORFilter(filter.getOr());
}
else if (filter.getPresent() != null)
{
result = createPresentFilter(filter.getPresent());
}
else if (filter.getSubstrings() != null)
{
result = createSubstringFilter(filter.getSubstrings());
}
return result;
}
/**
* Perform the LDAP SEARCH operation and send the result back to the
* client.
*
* @param objFactory
* The object factory for this operation.
* @param searchRequest
* The search request for this operation.
* @param controls
* Any required controls (e.g. for proxy authz).
* @return The result of the search operation.
* @throws IOException
* If an I/O problem occurs.
* @throws LDAPException
* If an error occurs while interacting with an LDAP
* element.
*/
public SearchResponse doSearch(ObjectFactory objFactory,
SearchRequest searchRequest,
List<org.opends.server.types.Control> controls)
throws IOException, LDAPException
{
SearchResponse searchResponse = objFactory.createSearchResponse();
searchResponse.setRequestID(searchRequest.getRequestID());
LDAPFilter filter = createFilter(searchRequest.getFilter());
DereferencePolicy derefPolicy = DereferencePolicy.NEVER_DEREF_ALIASES;
String derefStr = searchRequest.getDerefAliases().toLowerCase();
if (derefStr.equals("derefinsearching"))
{
derefPolicy = DereferencePolicy.DEREF_IN_SEARCHING;
}
else if (derefStr.equals("dereffindingbaseobj"))
{
derefPolicy = DereferencePolicy.DEREF_FINDING_BASE_OBJECT;
}
else if (derefStr.equals("derefalways"))
{
derefPolicy = DereferencePolicy.DEREF_ALWAYS;
}
SearchScope scope = SearchScope.WHOLE_SUBTREE;
String scopeStr = searchRequest.getScope().toLowerCase();
if (scopeStr.equals("singlelevel") || scopeStr.equals("one"))
{
scope = SearchScope.SINGLE_LEVEL;
}
else if (scopeStr.equals("baseobject") || scopeStr.equals("base"))
{
scope = SearchScope.BASE_OBJECT;
}
LinkedHashSet<String> attributes = new LinkedHashSet<String>();
// Get the list of attributes.
AttributeDescriptions attrDescriptions = searchRequest.getAttributes();
if (attrDescriptions != null)
{
List<AttributeDescription> attrDesc = attrDescriptions.getAttribute();
for (AttributeDescription desc : attrDesc)
{
attributes.add(desc.getName());
}
}
SearchRequestProtocolOp protocolOp = new SearchRequestProtocolOp(ByteString
.valueOf(searchRequest.getDn()), scope, derefPolicy,
(int) searchRequest.getSizeLimit(), (int) searchRequest.getTimeLimit(),
searchRequest.isTypesOnly(), filter, attributes);
try
{
LDAPMessage msg =
new LDAPMessage(DSMLServlet.nextMessageID(), protocolOp, controls);
connection.getLDAPWriter().writeMessage(msg);
byte opType;
do
{
int resultCode = 0;
Message errorMessage = null;
LDAPMessage responseMessage = connection.getLDAPReader().readMessage();
if(responseMessage == null)
{
//The server disconnected silently. At this point we don't know if it
// is a protocol error or anything else. Since we didn't hear from
// the server , we have a reason to believe that the server doesn't
// want to handle this request. Let us return unavailable error
// code to the client to cover possible cases.
Message message = ERR_UNEXPECTED_CONNECTION_CLOSURE.get();
LDAPResult result = objFactory.createLDAPResult();
ResultCode code = ResultCodeFactory.create(objFactory,
LDAPResultCode.UNAVAILABLE);
result.setResultCode(code);
result.setErrorMessage(message.toString());
searchResponse.setSearchResultDone(result);
return searchResponse;
}
opType = responseMessage.getProtocolOpType();
switch (opType)
{
case LDAPConstants.OP_TYPE_SEARCH_RESULT_ENTRY:
SearchResultEntryProtocolOp searchEntryOp = responseMessage
.getSearchResultEntryProtocolOp();
SearchResultEntry entry = objFactory.createSearchResultEntry();
java.util.List<DsmlAttr> attrList = entry.getAttr();
LinkedList<LDAPAttribute> attrs = searchEntryOp.getAttributes();
for (LDAPAttribute attr : attrs)
{
String nm = attr.getAttributeType();
DsmlAttr dsmlAttr = objFactory.createDsmlAttr();
dsmlAttr.setName(nm);
List<Object> dsmlAttrVal = dsmlAttr.getValue();
ArrayList<ByteString> vals = attr.getValues();
for (ByteString val : vals)
{
dsmlAttrVal.add(ByteStringUtility.convertByteString(val));
}
attrList.add(dsmlAttr);
}
entry.setDn(searchEntryOp.getDN().toString());
searchResponse.getSearchResultEntry().add(entry);
break;
case LDAPConstants.OP_TYPE_SEARCH_RESULT_REFERENCE:
responseMessage.getSearchResultReferenceProtocolOp();
break;
case LDAPConstants.OP_TYPE_SEARCH_RESULT_DONE:
SearchResultDoneProtocolOp searchOp = responseMessage
.getSearchResultDoneProtocolOp();
resultCode = searchOp.getResultCode();
errorMessage = searchOp.getErrorMessage();
LDAPResult result = objFactory.createLDAPResult();
ResultCode code = ResultCodeFactory.create(objFactory, resultCode);
result.setResultCode(code);
result.setErrorMessage(errorMessage != null ? errorMessage.toString()
: null);
if (searchOp.getMatchedDN() != null)
{
result.setMatchedDN(searchOp.getMatchedDN().toString());
}
searchResponse.setSearchResultDone(result);
break;
default:
throw new RuntimeException("Invalid protocol operation:" + opType);
}
}
while (opType != LDAPConstants.OP_TYPE_SEARCH_RESULT_DONE);
}
catch (ASN1Exception ae)
{
ae.printStackTrace();
throw new IOException(ae.getMessage());
}
return searchResponse;
}
}