/*
* 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 2010 Sun Microsystems, Inc.
*/
package org.opends.sdk.examples.server.store;
import static org.opends.sdk.ErrorResultException.newErrorResult;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.NavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.glassfish.grizzly.TransportFactory;
import org.opends.sdk.*;
import org.opends.sdk.ldif.LDIFEntryReader;
import org.opends.sdk.requests.*;
import org.opends.sdk.responses.*;
import com.sun.opends.sdk.tools.PerfToolTCPNIOTransportFactory;
/**
* An LDAP directory server which exposes data contained in an LDIF file. This
* is implementation is very simple and is only intended as an example:
*
* - It does not support SSL connections
*
- It does not support StartTLS
*
- It does not support Abandon or Cancel requests
*
- Very basic authentication and authorization support.
*
* This example takes the following command line parameters:
*
*
* <listenAddress> <listenPort> [<ldifFile>]
*
*/
public final class Main
{
/**
* Proxy server connection factory implementation.
*/
private static final class Store implements
ServerConnectionFactory
{
private final class ServerConnectionImpl implements
ServerConnection
{
private ServerConnectionImpl(final LDAPClientContext clientContext)
{
// Nothing to do.
}
/**
* {@inheritDoc}
*/
@Override
public void handleAbandon(final Integer requestContext,
final AbandonRequest request) throws UnsupportedOperationException
{
// Not implemented.
}
/**
* {@inheritDoc}
*/
@Override
public void handleAdd(final Integer requestContext,
final AddRequest request,
final ResultHandler super Result> resultHandler,
final IntermediateResponseHandler intermediateResponseHandler)
throws UnsupportedOperationException
{
// TODO: controls.
entryLock.writeLock().lock();
try
{
DN dn = request.getName();
if (entries.containsKey(dn))
{
resultHandler.handleErrorResult(ErrorResultException
.newErrorResult(ResultCode.ENTRY_ALREADY_EXISTS, "The entry "
+ dn.toString() + " already exists"));
}
DN parent = dn.parent();
if (!entries.containsKey(parent))
{
resultHandler.handleErrorResult(ErrorResultException
.newErrorResult(ResultCode.NO_SUCH_OBJECT, "The parent entry "
+ parent.toString() + " does not exist"));
}
else
{
entries.put(dn, request);
resultHandler.handleResult(Responses.newResult(ResultCode.SUCCESS));
}
}
finally
{
entryLock.writeLock().unlock();
}
}
/**
* {@inheritDoc}
*/
@Override
public void handleBind(final Integer requestContext, final int version,
final BindRequest request,
final ResultHandler super BindResult> resultHandler,
final IntermediateResponseHandler intermediateResponseHandler)
throws UnsupportedOperationException
{
if (request.getAuthenticationType() != ((byte) 0x80))
{
// TODO: SASL authentication not implemented.
resultHandler.handleErrorResult(newErrorResult(
ResultCode.PROTOCOL_ERROR,
"non-SIMPLE authentication not supported: "
+ request.getAuthenticationType()));
}
else
{
// TODO: always succeed.
resultHandler.handleResult(Responses
.newBindResult(ResultCode.SUCCESS));
}
}
/**
* {@inheritDoc}
*/
@Override
public void handleCompare(final Integer requestContext,
final CompareRequest request,
final ResultHandler super CompareResult> resultHandler,
final IntermediateResponseHandler intermediateResponseHandler)
throws UnsupportedOperationException
{
// TODO:
}
/**
* {@inheritDoc}
*/
@Override
public void handleConnectionClosed(final Integer requestContext,
final UnbindRequest request)
{
// Nothing to do.
}
/**
* {@inheritDoc}
*/
@Override
public void handleConnectionDisconnected(final ResultCode resultCode,
final String message)
{
// Nothing to do.
}
/**
* {@inheritDoc}
*/
@Override
public void handleConnectionError(final Throwable error)
{
// Nothing to do.
}
/**
* {@inheritDoc}
*/
@Override
public void handleDelete(final Integer requestContext,
final DeleteRequest request,
final ResultHandler super Result> resultHandler,
final IntermediateResponseHandler intermediateResponseHandler)
throws UnsupportedOperationException
{
// TODO: controls.
entryLock.writeLock().lock();
try
{
// TODO: check for children.
DN dn = request.getName();
if (!entries.containsKey(dn))
{
resultHandler.handleErrorResult(ErrorResultException
.newErrorResult(ResultCode.NO_SUCH_OBJECT,
"The entry " + dn.toString() + " does not exist"));
}
else
{
entries.remove(dn);
resultHandler.handleResult(Responses.newResult(ResultCode.SUCCESS));
}
}
finally
{
entryLock.writeLock().unlock();
}
}
/**
* {@inheritDoc}
*/
@Override
public void handleExtendedRequest(
final Integer requestContext, final ExtendedRequest request,
final ResultHandler super R> resultHandler,
final IntermediateResponseHandler intermediateResponseHandler)
throws UnsupportedOperationException
{
// TODO: not implemented.
resultHandler.handleErrorResult(newErrorResult(
ResultCode.PROTOCOL_ERROR,
"Extended request operation not supported"));
}
/**
* {@inheritDoc}
*/
@Override
public void handleModify(final Integer requestContext,
final ModifyRequest request,
final ResultHandler super Result> resultHandler,
final IntermediateResponseHandler intermediateResponseHandler)
throws UnsupportedOperationException
{
// TODO: controls.
// TODO: read lock is not really enough since concurrent updates may
// still occur to the same entry.
entryLock.readLock().lock();
try
{
DN dn = request.getName();
Entry entry = entries.get(dn);
if (entry == null)
{
resultHandler.handleErrorResult(ErrorResultException
.newErrorResult(ResultCode.NO_SUCH_OBJECT,
"The entry " + dn.toString() + " does not exist"));
}
Entry newEntry = new LinkedHashMapEntry(entry);
for (Modification mod : request.getModifications())
{
ModificationType modType = mod.getModificationType();
if (modType.equals(ModificationType.ADD))
{
// TODO: Reject empty attribute and duplicate values.
newEntry.addAttribute(mod.getAttribute(), null);
}
else if (modType.equals(ModificationType.DELETE))
{
// TODO: Reject missing values.
newEntry.removeAttribute(mod.getAttribute(), null);
}
else if (modType.equals(ModificationType.REPLACE))
{
newEntry.replaceAttribute(mod.getAttribute());
}
else
{
resultHandler.handleErrorResult(newErrorResult(
ResultCode.PROTOCOL_ERROR,
"Modify request contains an unsupported modification type"));
return;
}
}
entries.put(dn, newEntry);
resultHandler.handleResult(Responses.newResult(ResultCode.SUCCESS));
}
finally
{
entryLock.readLock().unlock();
}
}
/**
* {@inheritDoc}
*/
@Override
public void handleModifyDN(final Integer requestContext,
final ModifyDNRequest request,
final ResultHandler super Result> resultHandler,
final IntermediateResponseHandler intermediateResponseHandler)
throws UnsupportedOperationException
{
// TODO: not implemented.
resultHandler.handleErrorResult(newErrorResult(
ResultCode.PROTOCOL_ERROR,
"ModifyDN request operation not supported"));
}
/**
* {@inheritDoc}
*/
@Override
public void handleSearch(final Integer requestContext,
final SearchRequest request, final SearchResultHandler resultHandler,
final IntermediateResponseHandler intermediateResponseHandler)
throws UnsupportedOperationException
{
// TODO: controls, limits, etc.
entryLock.readLock().lock();
try
{
DN dn = request.getName();
Entry baseEntry = entries.get(dn);
if (baseEntry == null)
{
resultHandler.handleErrorResult(ErrorResultException
.newErrorResult(ResultCode.NO_SUCH_OBJECT,
"The entry " + dn.toString() + " does not exist"));
}
SearchScope scope = request.getScope();
if (scope.equals(SearchScope.BASE_OBJECT))
{
sendEntry(request, resultHandler, baseEntry);
}
else if (scope.equals(SearchScope.SINGLE_LEVEL))
{
sendEntry(request, resultHandler, baseEntry);
NavigableMap subtree = entries.tailMap(dn, false);
for (Entry entry : subtree.values())
{
DN childDN = entry.getName();
if (childDN.isChildOf(dn))
{
if (!sendEntry(request, resultHandler, entry))
{
// Caller has asked to stop sending results.
break;
}
}
else if (!childDN.isSubordinateOrEqualTo(dn))
{
// The remaining entries will be out of scope.
break;
}
}
}
else if (scope.equals(SearchScope.WHOLE_SUBTREE))
{
NavigableMap subtree = entries.tailMap(dn);
for (Entry entry : subtree.values())
{
DN childDN = entry.getName();
if (childDN.isSubordinateOrEqualTo(dn))
{
if (!sendEntry(request, resultHandler, entry))
{
// Caller has asked to stop sending results.
break;
}
}
else
{
// The remaining entries will be out of scope.
break;
}
}
}
else
{
resultHandler.handleErrorResult(newErrorResult(
ResultCode.PROTOCOL_ERROR,
"Search request contains an unsupported search scope"));
return;
}
resultHandler.handleResult(Responses.newResult(ResultCode.SUCCESS));
}
finally
{
entryLock.readLock().unlock();
}
}
private boolean sendEntry(SearchRequest request,
SearchResultHandler resultHandler, Entry entry)
{
// TODO: check filter, strip attributes.
return resultHandler.handleEntry(Responses.newSearchResultEntry(entry));
}
}
private final ConcurrentSkipListMap entries;
private final ReentrantReadWriteLock entryLock = new ReentrantReadWriteLock();
private Store(final ConcurrentSkipListMap entries)
{
this.entries = entries;
}
/**
* {@inheritDoc}
*/
@Override
public ServerConnection handleAccept(
final LDAPClientContext clientContext) throws ErrorResultException
{
return new ServerConnectionImpl(clientContext);
}
}
/**
* Main method.
*
* @param args
* The command line arguments: listen address, listen port, ldifFile
*/
public static void main(final String[] args)
{
if (args.length != 3)
{
System.err.println("Usage: listenAddress listenPort ldifFile");
System.exit(1);
}
// Use the same transport factory as the tools.
TransportFactory.setInstance(new PerfToolTCPNIOTransportFactory());
// Parse command line arguments.
final String localAddress = args[0];
final int localPort = Integer.parseInt(args[1]);
// Read the LDIF.
InputStream ldif;
try
{
ldif = new FileInputStream(args[2]);
}
catch (final FileNotFoundException e)
{
System.err.println(e.getMessage());
System.exit(ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue());
return;
}
final LDIFEntryReader reader = new LDIFEntryReader(ldif);
ConcurrentSkipListMap entries = new ConcurrentSkipListMap();
try
{
while (reader.hasNext())
{
Entry entry = reader.readEntry();
entries.put(entry.getName(), entry);
}
}
catch (final IOException e)
{
System.err.println(e.getMessage());
System.exit(ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue());
return;
}
finally
{
try
{
reader.close();
}
catch (final IOException ignored)
{
// Ignore.
}
}
// Quickly sanity check that every entry (except root entries) have a
// parent.
boolean isValid = true;
for (DN dn : entries.keySet())
{
if (dn.size() > 1)
{
DN parent = dn.parent();
if (!entries.containsKey(parent))
{
System.err.println("The entry \"" + dn.toString()
+ "\" does not have a parent");
isValid = false;
}
}
}
if (!isValid)
{
System.exit(1);
}
// Create listener.
final LDAPListenerOptions options = new LDAPListenerOptions()
.setBacklog(4096);
LDAPListener listener = null;
try
{
listener = new LDAPListener(localAddress, localPort, new Store(entries),
options);
System.out.println("Press any key to stop the server...");
System.in.read();
}
catch (final IOException e)
{
System.out
.println("Error listening on " + localAddress + ":" + localPort);
e.printStackTrace();
}
finally
{
if (listener != null)
{
listener.close();
}
}
}
private Main()
{
// Not used.
}
}