/*
|
* 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 legal-notices/CDDLv1_0.txt
|
* or http://forgerock.org/license/CDDLv1.0.html.
|
* 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 legal-notices/CDDLv1_0.txt.
|
* 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-2013 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();
|
List<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;
|
}
|
}
|