org.apache.hadoop.hbase.stargate.RESTServlet.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.hbase.stargate.RESTServlet.java

Source

/*
 * Copyright 2010 The Apache Software Foundation
 *
 * 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.hadoop.hbase.stargate;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.xml.bind.JAXBException;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Chore;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.client.HTableInterface;
import org.apache.hadoop.hbase.client.HTablePool;
import org.apache.hadoop.hbase.stargate.auth.Authenticator;
import org.apache.hadoop.hbase.stargate.auth.HBCAuthenticator;
import org.apache.hadoop.hbase.stargate.auth.HTableAuthenticator;
import org.apache.hadoop.hbase.stargate.auth.JDBCAuthenticator;
import org.apache.hadoop.hbase.stargate.auth.ZooKeeperAuthenticator;
import org.apache.hadoop.hbase.stargate.metrics.StargateMetrics;
import org.apache.hadoop.hbase.stargate.util.HTableTokenBucket;
import org.apache.hadoop.hbase.stargate.util.SoftUserData;
import org.apache.hadoop.hbase.stargate.util.UserData;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.hadoop.hbase.util.Threads;
import org.apache.hadoop.hbase.zookeeper.ZooKeeperWrapper;

import org.apache.hadoop.util.StringUtils;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.data.Stat;

import com.sun.jersey.api.json.JSONJAXBContext;
import com.sun.jersey.api.json.JSONMarshaller;
import com.sun.jersey.server.impl.container.servlet.ServletAdaptor;

/**
 * Singleton class encapsulating global REST servlet state and functions.
 */
public class RESTServlet extends ServletAdaptor implements Constants, Watcher {

    private static final Log LOG = LogFactory.getLog(RESTServlet.class);
    private static final long serialVersionUID = 1L;

    private static RESTServlet instance;

    @XmlRootElement(name = "status")
    static class StatusModel {
        @XmlAttribute
        long requests;
        @XmlElement
        List<String> connectors = new ArrayList<String>();

        public void addConnector(String host, int port) {
            connectors.add(host + ":" + Integer.toString(port));
        }
    }

    class StatusReporter extends Chore {

        final JSONJAXBContext context;
        final JSONMarshaller marshaller;

        public StatusReporter(int period, AtomicBoolean stopping) throws IOException {
            super(period, stopping);
            try {
                context = new JSONJAXBContext(StatusModel.class);
                marshaller = context.createJSONMarshaller();
            } catch (JAXBException e) {
                throw new IOException(e);
            }
        }

        @Override
        protected void chore() {
            if (wrapper != null)
                try {
                    StatusModel model = new StatusModel();
                    model.requests = (long) metrics.getRequests();
                    for (Pair<String, Integer> e : connectors) {
                        model.addConnector(e.getFirst(), e.getSecond());
                    }
                    ByteArrayOutputStream os = new ByteArrayOutputStream();
                    marshaller.marshallToJSON(model, os);
                    ensureExists(znode, CreateMode.EPHEMERAL, os.toByteArray());
                } catch (Exception e) {
                    LOG.error(StringUtils.stringifyException(e));
                }
        }
    }

    final String znode = INSTANCE_ZNODE_ROOT + "/" + System.currentTimeMillis();
    transient final Configuration conf;
    transient final HTablePool pool;
    transient volatile ZooKeeperWrapper wrapper;
    transient Chore statusReporter;
    transient Authenticator authenticator;
    AtomicBoolean stopping = new AtomicBoolean(false);
    boolean multiuser;
    Map<String, Integer> maxAgeMap = Collections.synchronizedMap(new HashMap<String, Integer>());
    List<Pair<String, Integer>> connectors = Collections.synchronizedList(new ArrayList<Pair<String, Integer>>());
    StargateMetrics metrics = new StargateMetrics();

    /**
     * @return the RESTServlet singleton instance
     * @throws IOException
     */
    public synchronized static RESTServlet getInstance() throws IOException {
        if (instance == null) {
            instance = new RESTServlet();
        }
        return instance;
    }

    private boolean ensureExists(final String znode, final CreateMode mode, final byte[] data) {
        try {
            ZooKeeper zk = wrapper.getZooKeeper();
            Stat stat = zk.exists(znode, false);
            if (stat != null) {
                zk.setData(znode, data, -1);
                return true;
            }
            zk.create(znode, data, Ids.OPEN_ACL_UNSAFE, mode);
            LOG.info("Created ZNode " + znode);
            return true;
        } catch (KeeperException.NodeExistsException e) {
            return true; // ok, move on.
        } catch (KeeperException.NoNodeException e) {
            return ensureParentExists(znode, CreateMode.PERSISTENT, new byte[] {})
                    && ensureExists(znode, mode, data);
        } catch (KeeperException e) {
            LOG.warn(StringUtils.stringifyException(e));
        } catch (InterruptedException e) {
            LOG.warn(StringUtils.stringifyException(e));
        }
        return false;
    }

    private boolean ensureParentExists(final String znode, final CreateMode mode, final byte[] data) {
        int index = znode.lastIndexOf('/');
        if (index <= 0) { // Parent is root, which always exists.
            return true;
        }
        return ensureExists(znode.substring(0, index), mode, data);
    }

    ZooKeeperWrapper initZooKeeperWrapper() throws IOException {
        return new ZooKeeperWrapper(conf, this);
    }

    /**
     * Constructor
     * @throws IOException
     */
    public RESTServlet() throws IOException {
        this.conf = HBaseConfiguration.create();
        this.pool = new HTablePool(conf, 10);
        this.wrapper = initZooKeeperWrapper();
        this.statusReporter = new StatusReporter(conf.getInt(STATUS_REPORT_PERIOD_KEY, 1000 * 30), stopping);
        Threads.setDaemonThreadRunning(statusReporter, "Stargate.statusReporter");
        this.multiuser = conf.getBoolean("stargate.multiuser", false);
        if (this.multiuser) {
            LOG.info("multiuser mode enabled");
            getAuthenticator();
        }
    }

    @Override
    public void process(WatchedEvent event) {
        LOG.debug(("ZooKeeper.Watcher event " + event.getType() + " with path " + event.getPath()));
        // handle disconnection (or manual delete to test disconnection scenario)
        if (event.getState() == KeeperState.Expired
                || (event.getType().equals(EventType.NodeDeleted) && event.getPath().equals(znode))) {
            wrapper.close();
            wrapper = null;
            while (!stopping.get())
                try {
                    wrapper = initZooKeeperWrapper();
                    break;
                } catch (IOException e) {
                    LOG.error(StringUtils.stringifyException(e));
                    try {
                        Thread.sleep(10 * 1000);
                    } catch (InterruptedException ex) {
                    }
                }
        }
    }

    HTablePool getTablePool() {
        return pool;
    }

    ZooKeeperWrapper getZooKeeperWrapper() {
        return wrapper;
    }

    Configuration getConfiguration() {
        return conf;
    }

    StargateMetrics getMetrics() {
        return metrics;
    }

    void addConnectorAddress(String host, int port) {
        connectors.add(new Pair<String, Integer>(host, port));
    }

    /**
     * @param tableName the table name
     * @return the maximum cache age suitable for use with this table, in
     *  seconds 
     * @throws IOException
     */
    public int getMaxAge(String tableName) throws IOException {
        Integer i = maxAgeMap.get(tableName);
        if (i != null) {
            return i.intValue();
        }
        HTableInterface table = pool.getTable(tableName);
        try {
            int maxAge = DEFAULT_MAX_AGE;
            for (HColumnDescriptor family : table.getTableDescriptor().getFamilies()) {
                int ttl = family.getTimeToLive();
                if (ttl < 0) {
                    continue;
                }
                if (ttl < maxAge) {
                    maxAge = ttl;
                }
            }
            maxAgeMap.put(tableName, maxAge);
            return maxAge;
        } finally {
            pool.putTable(table);
        }
    }

    /**
     * Signal that a previously calculated maximum cache age has been
     * invalidated by a schema change.
     * @param tableName the table name
     */
    public void invalidateMaxAge(String tableName) {
        maxAgeMap.remove(tableName);
    }

    /**
     * @return true if the servlet should operate in multiuser mode
     */
    public boolean isMultiUser() {
        return multiuser;
    }

    /**
     * @param multiuser true if the servlet should operate in multiuser mode 
     */
    public void setMultiUser(boolean multiuser) {
        this.multiuser = multiuser;
    }

    /**
     * @return an authenticator
     */
    public Authenticator getAuthenticator() {
        if (authenticator == null) {
            String className = conf.get(AUTHENTICATOR_KEY, HBCAuthenticator.class.getCanonicalName());
            try {
                Class<?> c = getClass().getClassLoader().loadClass(className);
                if (className.endsWith(HBCAuthenticator.class.getName())
                        || className.endsWith(HTableAuthenticator.class.getName())
                        || className.endsWith(JDBCAuthenticator.class.getName())) {
                    Constructor<?> cons = c.getConstructor(HBaseConfiguration.class);
                    authenticator = (Authenticator) cons.newInstance(new Object[] { conf });
                } else if (className.endsWith(ZooKeeperAuthenticator.class.getName())) {
                    Constructor<?> cons = c.getConstructor(HBaseConfiguration.class, ZooKeeperWrapper.class);
                    authenticator = (Authenticator) cons.newInstance(new Object[] { conf, wrapper });
                } else {
                    authenticator = (Authenticator) c.newInstance();
                }
            } catch (Exception e) {
                LOG.error(StringUtils.stringifyException(e));
            }
            if (authenticator == null) {
                authenticator = new HBCAuthenticator(conf);
            }
            LOG.info("using authenticator " + authenticator);
        }
        return authenticator;
    }

    /**
     * @param authenticator
     */
    public void setAuthenticator(Authenticator authenticator) {
        this.authenticator = authenticator;
    }

    /**
     * Check if the user has exceeded their request token limit within the
     * current interval
     * @param user the user
     * @param want the number of tokens desired
     * @throws IOException
     */
    public boolean userRequestLimit(final User user, int want) throws IOException {
        if (multiuser) {
            UserData ud = SoftUserData.get(user);
            HTableTokenBucket tb = (HTableTokenBucket) ud.get(UserData.TOKENBUCKET);
            if (tb == null) {
                tb = new HTableTokenBucket(conf, Bytes.toBytes(user.getToken()));
                ud.put(UserData.TOKENBUCKET, tb);
            }
            if (tb.available() < want) {
                return false;
            }
            tb.remove(want);
        }
        return true;
    }

}