Package org.apache.harmony.jndi.provider.ldap

Source Code of org.apache.harmony.jndi.provider.ldap.LdapClient

/*
*  Licensed to the Apache Software Foundation (ASF) under one or more
*  contributor license agreements.  See the NOTICE file distributed with
*  this work for additional information regarding copyright ownership.
*  The ASF licenses this file to You under the Apache License, Version 2.0
*  (the "License"); you may not use this file except in compliance with
*  the License.  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing, software
*  distributed under the License is distributed on an "AS IS" BASIS,
*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*  See the License for the specific language governing permissions and
*  limitations under the License.
*/

package org.apache.harmony.jndi.provider.ldap;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;

import javax.naming.CommunicationException;
import javax.naming.ConfigurationException;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.ldap.Control;
import javax.naming.ldap.StartTlsRequest;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;

import org.apache.harmony.jndi.internal.nls.Messages;
import org.apache.harmony.jndi.provider.ldap.LdapContextImpl.UnsolicitedListener;
import org.apache.harmony.jndi.provider.ldap.asn1.ASN1Decodable;
import org.apache.harmony.jndi.provider.ldap.asn1.ASN1Encodable;
import org.apache.harmony.jndi.provider.ldap.asn1.LdapASN1Constant;
import org.apache.harmony.jndi.provider.ldap.event.ECNotificationControl;
import org.apache.harmony.jndi.provider.ldap.event.PersistentSearchControl;
import org.apache.harmony.jndi.provider.ldap.event.PersistentSearchResult;
import org.apache.harmony.security.asn1.ASN1Integer;

/**
* LdapClient is the actual class used to communicate with Ldap Server.
*
*/
public class LdapClient {
    /**
     * Socket used to communicate with Ldap Server.
     */
    private Socket socket;

    /**
     * Input stream of socket.
     */
    private InputStream in;

    /**
     * Output stream of socket.
     */
    private OutputStream out;

    /**
     * Address of connection
     */
    private String address;

    /**
     * port of connection
     */
    private int port;

    /**
     * blocked requests list which wait for response
     */
    private Hashtable<Integer, Element> requests = new Hashtable<Integer, Element>();

    /**
     * the max time to wait server response in milli-second
     */
    private long MAX_WAIT_TIME = 30 * 1000;

    /**
     * responsible for dispatching received messages
     */
    private Dispatcher dispatcher;

    /**
     * registered UnsolicitedListener
     */
    private List<UnsolicitedListener> unls = new ArrayList<UnsolicitedListener>();

    // constructor for test
    public LdapClient() {
        // do nothing
    }

    /**
     * Constructor for LdapClient.
     *
     * @param factory
     *            used to construct socket through its factory method
     * @param address
     *            the Internet Protocol (IP) address of ldap server
     * @param port
     *            the port number of ldap server
     * @throws UnknownHostException
     *             if the host cannot be resolved
     * @throws IOException
     *             if an error occurs while instantiating the socket
     */
    public LdapClient(SocketFactory factory, String address, int port)
            throws UnknownHostException, IOException {
        this.address = address;
        this.port = port;
        socket = factory.createSocket(address, port);
        // FIXME: Use of InputStreamWrap here is to deal with a potential bug of
        // RI.
        in = new InputStreamWrap(socket.getInputStream());
        out = socket.getOutputStream();
        dispatcher = new Dispatcher();
        dispatcher.start();
    }

    /**
     * The instance of the class is daemon thread, which read messages from
     * server and dispatch to corresponding thread.
     */
    class Dispatcher extends Thread {

        private boolean isStopped = false;

        public Dispatcher() {
            /**
             * must be daemon thread, otherwrise can't destory by gc
             */
            setDaemon(true);
        }

        public boolean isStopped() {
            return isStopped;
        }

        public void setStopped(boolean isStopped) {
            this.isStopped = isStopped;
        }

        @Override
        public void run() {
            while (!isStopped) {
                try {
                    // set response op to null, load later
                    LdapMessage response = new LdapMessage(null) {

                        /**
                         * Dispatcher can't know which response operation should
                         * be used until messageId had determined.
                         *
                         * @return response according messageId
                         */
                        @Override
                        public ASN1Decodable getResponseOp() {
                            // responseOp has been load, just return it
                            if (super.getResponseOp() != null) {
                                return super.getResponseOp();
                            }

                            int messageId = getMessageId();

                            // Unsolicited Notification
                            if (messageId == 0) {
                                return new UnsolicitedNotificationImpl();
                            }

                            // get response operation according messageId
                            Element element = requests.get(Integer
                                    .valueOf(messageId));
                            if (element != null) {
                                return element.response.getResponseOp();
                            }

                            /*
                             * FIXME: if messageId not find in request list,
                             * what should we do?
                             */
                            return null;
                        }
                    };

                    Exception ex = null;
                    /**
                     * TODO read message data by ourselves then decode, this
                     * would be robust
                     */
                    try {
                        // read next message
                        response.decode(in);
                    } catch (IOException e) {
                        // may socket has problem or decode occurs error
                        ex = e;
                    } catch (RuntimeException e) {
                        // may socket has problem or decode occurs error
                        ex = e;
                    }

                    processResponse(response, ex);

                } catch (Exception e) {
                    // may never reach
                    e.printStackTrace();
                }
            }

        }

        private void processResponse(LdapMessage response, Exception ex) {
            // unsolicited notification
            if (response.getMessageId() == 0) {
                notifyUnls(response);
                return;
            }

            Element element = requests.get(Integer.valueOf(response
                    .getMessageId()));

            if (element != null) {
                element.response = response;
                element.ex = ex;
                // persistent search response
                if (element.lock == null) {

                    notifyPersistenSearchListener(element);

                } else {
                    /*
                     * notify the thread which send request and wait for
                     * response
                     */
                    if (element.response.getOperationIndex() == LdapASN1Constant.OP_EXTENDED_RESPONSE
                            && ((ExtendedOp) element.response.getResponseOp())
                                    .getExtendedRequest().getID().equals(
                                            StartTlsRequest.OID)) {
                        /*
                         * When establishing TLS by StartTls extended operation,
                         * no
                         */
                        isStopped = true;
                    }

                    synchronized (element.lock) {
                        element.lock.notify();
                    }
                } // end of if (element.lock == null) else
            } // end of if (element != null)

            else if (ex != null) {
                /*
                 * may asn1 decode error or socket problem, can get message id,
                 * so couldn't know which thread should be notified
                 */
                // FIXME: any better way?
                close();
            }
            // FIXME message id not found and no exception, what shoud we do?

        } // end of processResponse
    } // Dispatcher

    private void notifyUnls(LdapMessage response) {
        UnsolicitedNotificationImpl un = (UnsolicitedNotificationImpl) response
                .getResponseOp();
        for (UnsolicitedListener listener : unls) {
            listener.receiveNotification(un, response.getControls());
        }
    }

    /**
     * Carry out the ldap operation encapsulated in operation with controls.
     *
     * @param operation
     *            the ldap operation
     * @param controls
     *            extra controls for some ldap operations
     * @return the encapsulated response message from ldap server
     * @throws IOException
     */
    public LdapMessage doOperation(LdapOperation operation, Control[] controls)
            throws IOException {
        return doOperation(operation.getRequestId(), operation.getRequest(),
                operation.getResponse(), controls);
    }

    /**
     * Send out the ldap operation in request with controls, and decode response
     * into LdapMessage.
     *
     * @param opIndex
     * @param request
     *            the ldap request
     * @param response
     *            the ldap response
     * @param controls
     *            extra controls for some ldap operations
     * @return the encapsulated response message from ldap server
     * @throws IOException
     */
    public LdapMessage doOperation(int opIndex, ASN1Encodable request,
            ASN1Decodable response, Control[] controls) throws IOException {

        if (opIndex == LdapASN1Constant.OP_SEARCH_REQUEST) {
            return doSearchOperation(request, response, controls);
        }

        LdapMessage requestMsg = new LdapMessage(opIndex, request, controls);

        Integer messageID = Integer.valueOf(requestMsg.getMessageId());

        Object lock = new Object();
        requests.put(messageID, new Element(lock, new LdapMessage(response)));

        try {
            out.write(requestMsg.encode());
            out.flush();
            return waitResponse(messageID, lock);

        } finally {
            // remove request from list
            requests.remove(messageID);
        }

    }

    /**
     * Block the current thread until get response from server or occurs error
     *
     * @param messageID
     *            id of request message, is same as id of response message
     * @param response
     *            decoder of the response
     * @return response message, may not be null
     *
     * @throws Exception
     */
    private LdapMessage waitResponse(Integer messageID, Object lock)
            throws IOException {
        Element element = requests.get(messageID);

        /*
         * test if dispatcher has not received response message from server,
         * wait response
         */
        if (element.response.getMessageId() != messageID.intValue()) {

            synchronized (lock) {
                try {
                    lock.wait(MAX_WAIT_TIME);
                } catch (InterruptedException e) {
                    // ignore
                }
            }
        }
       
        element = requests.get(messageID);

        // wait time out
        if (element.response.getMessageId() != messageID.intValue()) {
            // ldap.31=Read LDAP response message time out
            throw new IOException(Messages.getString("ldap.31")); //$NON-NLS-1$
        }

        // error occurs when read response
        if (element.ex != null) {
            // socket is not connected
            if (!socket.isConnected()) {
                close();
            }
            // element.ex must be one of IOException or RuntimeException
            if (element.ex instanceof IOException) {
                throw (IOException) element.ex;
            }

            throw (RuntimeException) element.ex;
        }

        return element.response;

    }

    private LdapMessage doSearchOperation(ASN1Encodable request,
            ASN1Decodable response, Control[] controls) throws IOException {
        LdapMessage requestMsg = new LdapMessage(
                LdapASN1Constant.OP_SEARCH_REQUEST, request, controls);

        Integer messageID = Integer.valueOf(requestMsg.getMessageId());

        Object lock = new Object();
        requests.put(messageID, new Element(lock, new LdapMessage(response)));

        try {
            out.write(requestMsg.encode());
            out.flush();
            LdapMessage responseMsg = waitResponse(messageID, lock);

            while (responseMsg.getOperationIndex() != LdapASN1Constant.OP_SEARCH_RESULT_DONE) {
                responseMsg = waitResponse(messageID, lock);
            }

            return responseMsg;
        } finally {
            // remove request from list
            requests.remove(messageID);
        }

    }

    public void abandon(final int messageId, Control[] controls)
            throws IOException {
        doOperationWithoutResponse(LdapASN1Constant.OP_ABANDON_REQUEST,
                new ASN1Encodable() {

                    public void encodeValues(Object[] values) {
                        values[0] = ASN1Integer.fromIntValue(messageId);
                    }

                }, controls);
    }

    public void doOperationWithoutResponse(int opIndex, ASN1Encodable op,
            Control[] controls) throws IOException {
        LdapMessage request = new LdapMessage(opIndex, op, controls);
        out.write(request.encode());
        out.flush();
    }

    public int addPersistentSearch(SearchOp op) throws IOException {
        LdapMessage request = new LdapMessage(
                LdapASN1Constant.OP_SEARCH_REQUEST, op.getRequest(),
                new Control[] { new PersistentSearchControl() });

        Integer messageID = Integer.valueOf(request.getMessageId());

        // set lock to null, indicate this is persistent search
        requests.put(messageID, new Element(null, new LdapMessage(op
                .getResponse())));
        try {
            out.write(request.encode());
            out.flush();
            return request.getMessageId();
        } catch (IOException e) {
            // send request faild, remove request from list
            requests.remove(messageID);
            throw e;
        }

    }

    public void removePersistentSearch(int messageId, Control[] controls)
            throws IOException {
        requests.remove(Integer.valueOf(messageId));
        abandon(messageId, controls);
    }

    /**
     * Close network connection, stop dispather thread, and release all other
     * resources
     *
     * NOTE: invoke this method should be careful when this
     * <code>LdapClient</code> instance is shared by multi
     * <code>LdapContext</code>
     *
     */
    public void close() {
        // close socket
        if (socket != null) {
            try {
                socket.close();
            } catch (IOException e) {
                // ignore
            }
        }

        socket = null;
        in = null;
        out = null;

        // try to stop dispather
        if (dispatcher != null) {
            dispatcher.setStopped(true);
            dispatcher.interrupt();
        }

        // notify all blocked thread
        if (requests != null) {
            for (Element element : requests.values()) {
                if (element.lock != null) {
                    synchronized (element.lock) {
                        element.lock.notify();
                    }
                } else {
                    // TODO notify persistent search listeners
                }
            }
            requests.clear();
            requests = null;
        }

    }

    /**
     * Get new instance of LdapClient according environment variable
     *
     * @param envmt
     * @return
     * @throws NamingException
     */
    public static LdapClient newInstance(String host, int port,
            Hashtable<?, ?> envmt) throws NamingException {
        String factoryName = (String) envmt
                .get("java.naming.ldap.factory.socket");

        SocketFactory factory = null;
        if (factoryName == null || "".equals(factoryName)) {
            if ("ssl".equalsIgnoreCase((String) envmt
                    .get(Context.SECURITY_PROTOCOL))) {
                factory = SSLSocketFactory.getDefault();
            } else {
                factory = SocketFactory.getDefault();
            }
        } else {

            try {
                factory = (SocketFactory) classForName(factoryName)
                        .newInstance();
            } catch (Exception e) {
                ConfigurationException ex = new ConfigurationException();
                ex.setRootCause(e);
                throw ex;
            }
        }
        // TODO: get LdapClient from pool first.

        try {
            return new LdapClient(factory, host, port);

        } catch (IOException e) {
            CommunicationException ex = new CommunicationException();
            ex.setRootCause(e);
            throw ex;
        }
    }

    private static Class<?> classForName(final String className)
            throws ClassNotFoundException {

        Class<?> cls = null;
        // try thread context class loader first
        try {
            cls = Class.forName(className, true, Thread.currentThread()
                    .getContextClassLoader());
        } catch (ClassNotFoundException e) {
            // Ignored.
        }
        // try system class loader second
        try {
            cls = Class.forName(className, true, ClassLoader
                    .getSystemClassLoader());
        } catch (ClassNotFoundException e1) {
            // Ignored.
        }

        if (cls == null) {
            // jndi.1C=class {0} not found
            throw new ClassNotFoundException(Messages.getString(
                    "jndi.1C", className)); //$NON-NLS-1$
        }

        return cls;
    }

    /**
     * struct for holding necessary info to add to requests list
     */
    static class Element {
        Object lock;

        LdapMessage response;

        Exception ex;

        public Element(Object lock, LdapMessage response) {
            this.lock = lock;
            this.response = response;
        }
    }

    // TODO: This class is used to deal with a potential bug of RI, may be
    // removed in the future.
    /**
     * When use <code>InputStream</code> from SSL Socket, if invoke
     * <code>InputStream.read(byte[])</code> with byte array of zero length,
     * the method will be blocked. Seems it's bug of ri.
     *
     * This wrap class delegate all request to wrapped instance, except
     * returning immediately when the invoke
     * <code>InputStream.read(byte[])</code> with byte array of zero length.
     */
    static class InputStreamWrap extends InputStream {
        InputStream in;

        public InputStreamWrap(InputStream in) {
            this.in = in;
        }

        @Override
        public int read() throws IOException {
            return in.read();
        }

        @Override
        public int read(byte[] bs, int offset, int len) throws IOException {
            if (len == 0) {
                return 0;
            }
            return in.read(bs, offset, len);
        }

        @Override
        public void reset() throws IOException {
            in.reset();

        }

        @Override
        public int available() throws IOException {
            return in.available();
        }

        @Override
        public void close() throws IOException {
            in.close();
        }

        @Override
        public void mark(int readlimit) {
            in.mark(readlimit);
        }

        @Override
        public boolean markSupported() {
            return in.markSupported();
        }

        @Override
        public int read(byte[] b) throws IOException {
            return in.read(b);
        }

        @Override
        public long skip(long n) throws IOException {
            return in.skip(n);
        }
    }

    public String getAddress() {
        return address;
    }

    public int getPort() {
        return port;
    }

    @Override
    protected void finalize() {
        close();
    }

    public Socket getSocket() {
        return this.socket;
    }

    public void setSocket(Socket socket) throws IOException {
        this.socket = socket;
        this.in = new InputStreamWrap(socket.getInputStream());
        this.out = socket.getOutputStream();
        if (dispatcher != null) {
            dispatcher.setStopped(true);
            dispatcher.interrupt();
        }
        this.dispatcher = new Dispatcher();
        this.dispatcher.start();
    }

    public void addUnsolicitedListener(UnsolicitedListener listener) {
        if (unls == null) {
            unls = new ArrayList<UnsolicitedListener>();
        }

        if (!unls.contains(listener)) {
            unls.add(listener);
        }
    }

    private void notifyPersistenSearchListener(Element element) {
        PersistentSearchResult psr = (PersistentSearchResult) ((SearchOp) element.response
                .getResponseOp()).getSearchResult();
        // test error
        if (psr.getResult() != null) {
            psr.receiveNotificationHook(psr.getResult());
        }

        // notify listener
        Control[] cs = element.response.getControls();
        if (cs != null) {
            for (int i = 0; i < cs.length; i++) {
                Control control = cs[i];
                if (ECNotificationControl.OID.equals(control.getID())) {
                    psr.receiveNotificationHook(new ECNotificationControl(
                            control.getEncodedValue()));
                }
            }
        }
    }

}
TOP

Related Classes of org.apache.harmony.jndi.provider.ldap.LdapClient

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.