org.apache.camel.component.social.providers.twitter.AbstractTwitterPath.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.camel.component.social.providers.twitter.AbstractTwitterPath.java

Source

/**
 * 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.camel.component.social.providers.twitter;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;

import oauth.signpost.OAuthConsumer;
import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer;

import org.apache.camel.component.social.DefaultSocialData;
import org.apache.camel.component.social.SocialData;
import org.apache.camel.component.social.SocialHttpClient;
import org.apache.camel.component.social.SocialOAuth;
import org.apache.camel.component.social.path.RateLimitExceededException;
import org.apache.camel.component.social.path.SocialDataFetchError;
import org.apache.camel.component.social.path.SocialPathConsumer;
import org.apache.camel.component.social.path.SocialPathSessionAware;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

public abstract class AbstractTwitterPath implements SocialPathConsumer, SocialPathSessionAware {

    private static final String STATUS_UPDATE = "statuses/update";

    private static Log log = LogFactory.getLog(AbstractTwitterPath.class);

    protected static final String FORMAT = ".xml";

    protected static final String TWITTER_API_URL = "http://api.twitter.com/1/";

    private static final String SINCE_ID_PARAM = "since_id";

    private volatile boolean sessionActive;
    private String path;
    private TwitterProvider provider;
    private SocialHttpClient httpClient;

    private DocumentBuilderFactory domFac;

    public DocumentBuilderFactory getDomFac() {
        return domFac;
    }

    protected String getFormat() {
        return FORMAT;
    }

    private XPathFactory factory;
    private XPath xpath;

    public XPath getXpath() {
        return xpath;
    }

    private Transformer transformer;

    public Transformer getTransformer() {
        return transformer;
    }

    private long started = 0;

    AbstractTwitterPath(TwitterProvider twitterProvider, String path) throws Exception {
        this.path = path;
        this.provider = twitterProvider;

        domFac = DocumentBuilderFactory.newInstance();
        factory = XPathFactory.newInstance();
        xpath = factory.newXPath();
        transformer = TransformerFactory.newInstance().newTransformer();
    }

    public String getPath() {
        return path;
    }

    public void endSession() {
        httpClient.shutdown();
        sessionActive = false;
    }

    public void initSession(SocialOAuth consumerCredentials, SocialOAuth userCredentials) throws Exception {
        OAuthConsumer consumer = provider.createOAuthConsumer(consumerCredentials);
        consumer.setTokenWithSecret(userCredentials.token, userCredentials.secret);
        httpClient = new SocialHttpClient((CommonsHttpOAuthConsumer) consumer);
        sessionActive = true;
    }

    public boolean isSessionActive() {
        return sessionActive;
    }

    protected String getCommandPath() {
        return STATUS_UPDATE;
    }

    public SocialData updateData(Object data, Map<String, Object> headers) throws SocialDataFetchError {
        String status = data.toString();

        String url = normalizeURL(getCommandPath());

        Map<String, Object> params = new HashMap<String, Object>();

        Object inReplyTo = headers.get(TwitterProvider.IN_REPLY_TO);
        if (inReplyTo != null) {
            params.put(TwitterProvider.IN_REPLY_TO, inReplyTo);
        }

        Object lat = headers.get(TwitterProvider.LATITUDE);
        if (lat != null) {
            params.put(TwitterProvider.LATITUDE, lat);
        }

        Object _long = headers.get(TwitterProvider.LONGITUDE);
        if (_long != null) {
            params.put(TwitterProvider.LONGITUDE, _long);
        }

        Object place_id = headers.get(TwitterProvider.PLACE_ID);
        if (place_id != null) {
            params.put(TwitterProvider.PLACE_ID, place_id);
        }

        Object trim_user = headers.get(TwitterProvider.TRIM_USER);
        params.put(TwitterProvider.TRIM_USER, trim_user == null ? "1" : trim_user);
        params.put(TwitterProvider.STATUS, status);

        HttpPost post = new HttpPost(url);
        HttpResponse response;
        response = callHttpMethod(params, post);

        if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
            try {
                log.warn("Could not update Twitter status: " + response.getStatusLine() + "\n"
                        + EntityUtils.toString(response.getEntity()));
            } catch (ParseException e) {
            } catch (IOException e) {
            }
            return null;
        }

        String body;
        try {
            body = EntityUtils.toString(response.getEntity());

            DocumentBuilder db = domFac.newDocumentBuilder();

            InputSource source = new InputSource();
            source.setCharacterStream(new StringReader(body));
            Document doc = db.parse(source);

            DefaultSocialData socialData = parseStatus(doc.getFirstChild());
            return socialData;
        } catch (Exception e) {
            throw new SocialDataFetchError(e);
        }
    }

    public Iterable<SocialData> readData(String lastId) throws SocialDataFetchError, RateLimitExceededException {
        return readData(lastId, null);
    }

    public Iterable<SocialData> readData(String lastId, Map<String, Object> providerParams)
            throws SocialDataFetchError, RateLimitExceededException {
        if (httpClient == null) {
            initHttpClientNoAuth();
        }

        Map<String, Object> params = new HashMap<String, Object>();
        if (lastId != null) {
            params.put(SINCE_ID_PARAM, lastId);
        }

        if (providerParams != null) {
            params.putAll(providerParams);
        }

        if (started == 0) {
            started = System.currentTimeMillis();
        }

        String body = listStatuses(getStreamPath(), params);

        try {
            return convertToSocialDataList(body);
        } catch (Exception e) {
            throw new SocialDataFetchError(e);
        }
    }

    private void initHttpClientNoAuth() {
        httpClient = new SocialHttpClient();
    }

    protected final String listStatuses(String stream, Map<String, Object> params)
            throws SocialDataFetchError, RateLimitExceededException {
        String url = normalizeURL(stream);

        // TODO fix
        if (params != null && params.get("q") != null) {
            if (url.indexOf('?') == -1) {
                url = url.concat("?");
            }

            url = url.concat("q=").concat(params.get("q").toString());
        }
        HttpGet get = new HttpGet(url);
        HttpResponse response;
        try {
            response = callHttpMethod(params, get);
        } catch (Exception e1) {
            throw new SocialDataFetchError(e1);
        }

        String body;
        try {
            body = EntityUtils.toString(response.getEntity());
        } catch (Exception e) {
            throw new SocialDataFetchError(e);
        }

        if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
            log.warn("Twitter provider could not fetch social data: " + response.getStatusLine());

            if (log.isDebugEnabled()) {
                log.debug("Body Request: " + body);

                log.debug("Headers: ");
                for (Header h : response.getAllHeaders()) {
                    log.warn(h.getName() + ": " + h.getValue());
                }
            }

            Header[] hRate = response.getHeaders("X-RateLimit-Remaining");
            if (hRate != null && hRate.length > 0 && Long.parseLong(hRate[0].getValue()) == 0) {
                log.warn("Twitter provider found rate limit exceeded");

                long timeNow = System.currentTimeMillis();
                long howLong = timeNow - started;
                long haveToWait = TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS) - howLong;

                Header[] hRateReset = response.getHeaders("X-RateLimit-Reset");

                if (hRateReset != null && hRateReset.length > 0) {
                    try {
                        haveToWait = Long.parseLong(hRateReset[0].getValue());
                    } catch (Exception e) {
                    }
                }
                log.warn("throwing RateLimitExceededException(" + haveToWait + ")");

                throw new RateLimitExceededException(haveToWait);
            }
        }

        get.abort();

        return body;
    }

    protected HttpResponse callHttpMethod(Map<String, Object> params, HttpUriRequest method)
            throws SocialDataFetchError {
        method.getParams().setBooleanParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, false);

        List<NameValuePair> postMethod = new ArrayList<NameValuePair>();

        for (Map.Entry<String, Object> e : params.entrySet()) {
            if (method instanceof HttpEntityEnclosingRequestBase) {
                postMethod.add(new BasicNameValuePair(e.getKey(), e.getValue().toString()));
            } else {
                method.getParams().setParameter(e.getKey(), e.getValue());
            }
        }

        if (method instanceof HttpEntityEnclosingRequestBase) {
            try {
                ((HttpEntityEnclosingRequestBase) method)
                        .setEntity(new UrlEncodedFormEntity(postMethod, HTTP.UTF_8));
            } catch (UnsupportedEncodingException e1) {
            }
        }

        HttpResponse response;
        try {
            response = httpClient.execute(method);
        } catch (Exception e) {
            throw new SocialDataFetchError(e);
        }
        return response;
    }

    protected String normalizeURL(String stream) {
        if (stream.charAt(0) == '/') {
            stream = stream.substring(1);
        }

        StringBuilder url = new StringBuilder();
        url.append(getTwitterApiUrl()).append(stream).append(getFormat());
        return url.toString();
    }

    protected Iterable<SocialData> convertToSocialDataList(String body) throws Exception {
        DocumentBuilder db = getDomFac().newDocumentBuilder();

        InputSource source = new InputSource();
        source.setCharacterStream(new StringReader(body));
        Document doc = db.parse(source);

        XPathExpression expr = getXpath().compile("/statuses/status");

        Object result = expr.evaluate(doc, XPathConstants.NODESET);
        NodeList nodes = (NodeList) result;

        List<SocialData> socialDataList = new ArrayList<SocialData>(nodes.getLength());
        for (int index = 0; index < nodes.getLength(); index++) {
            Node aNode = nodes.item(index);

            DefaultSocialData socialData = parseStatus(aNode);
            socialDataList.add(socialData);
        }

        return socialDataList;
    }

    protected DefaultSocialData parseStatus(Node aNode) throws TransformerException {
        StreamResult streamResult = new StreamResult(new StringWriter());
        DOMSource domSource = new DOMSource(aNode);
        getTransformer().transform(domSource, streamResult);

        String xmlString = streamResult.getWriter().toString();
        String id = null;
        NodeList childs = aNode.getChildNodes();
        for (int j = 0; j < childs.getLength(); j++) {
            Node child = childs.item(j);
            if (child.getNodeName().equals("id")) {
                id = child.getTextContent();
                break;
            }
        }

        DefaultSocialData socialData = new DefaultSocialData(id, xmlString);
        return socialData;
    }

    protected abstract String getStreamPath();

    public boolean isAuthRequired() {
        return false;
    }

    protected String getTwitterApiUrl() {
        return TWITTER_API_URL;
    }

}