/* * 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 * * 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.streams.facebook.provider; import org.apache.streams.config.StreamsConfigurator; import org.apache.streams.core.DatumStatusCounter; import org.apache.streams.core.StreamsDatum; import org.apache.streams.core.StreamsProvider; import org.apache.streams.core.StreamsResultSet; import org.apache.streams.facebook.FacebookUserInformationConfiguration; import org.apache.streams.facebook.FacebookUserstreamConfiguration; import org.apache.streams.jackson.StreamsJacksonMapper; import org.apache.streams.util.ComponentUtils; import com.fasterxml.jackson.databind.ObjectMapper; import com.typesafe.config.Config; import com.typesafe.config.ConfigRenderOptions; import facebook4j.Facebook; import facebook4j.FacebookException; import facebook4j.FacebookFactory; import facebook4j.Friend; import facebook4j.Paging; import facebook4j.Post; import facebook4j.ResponseList; import facebook4j.conf.ConfigurationBuilder; import facebook4j.json.DataObjectFactory; import org.apache.commons.lang.NotImplementedException; import org.joda.time.DateTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.Serializable; import java.math.BigInteger; import java.util.Iterator; import java.util.Objects; import java.util.Queue; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class FacebookFriendFeedProvider implements StreamsProvider, Serializable { private static final String STREAMS_ID = "FacebookFriendFeedProvider"; private static final Logger LOGGER = LoggerFactory.getLogger(FacebookFriendFeedProvider.class); private static final ObjectMapper mapper = StreamsJacksonMapper.getInstance(); private static final String ALL_PERMISSIONS = "ads_management,ads_read,create_event,create_note,email,export_stream,friends_about_me,friends_actions.books,friends_actions.music,friends_actions.news,friends_actions.video,friends_activities,friends_birthday,friends_education_history,friends_events,friends_games_activity,friends_groups,friends_hometown,friends_interests,friends_likes,friends_location,friends_notes,friends_online_presence,friends_photo_video_tags,friends_photos,friends_questions,friends_relationship_details,friends_relationships,friends_religion_politics,friends_status,friends_subscriptions,friends_videos,friends_website,friends_work_history,manage_friendlists,manage_notifications,manage_pages,photo_upload,publish_actions,publish_stream,read_friendlists,read_insights,read_mailbox,read_page_mailboxes,read_requests,read_stream,rsvp_event,share_item,sms,status_update,user_about_me,user_actions.books,user_actions.music,user_actions.news,user_actions.video,user_activities,user_birthday,user_education_history,user_events,user_friends,user_games_activity,user_groups,user_hometown,user_interests,user_likes,user_location,user_notes,user_online_presence,user_photo_video_tags,user_photos,user_questions,user_relationship_details,user_relationships,user_religion_politics,user_status,user_subscriptions,user_videos,user_website,user_work_history,video_upload,xmpp_login"; private FacebookUserstreamConfiguration configuration; private Class klass; protected final ReadWriteLock lock = new ReentrantReadWriteLock(); protected volatile Queue<StreamsDatum> providerQueue = new LinkedBlockingQueue<>(); public FacebookUserstreamConfiguration getConfig() { return configuration; } public void setConfig(FacebookUserstreamConfiguration config) { this.configuration = config; } protected Iterator<String[]> idsBatches; protected ExecutorService executor; protected DateTime start; protected DateTime end; protected final AtomicBoolean running = new AtomicBoolean(); private DatumStatusCounter countersCurrent = new DatumStatusCounter(); private DatumStatusCounter countersTotal = new DatumStatusCounter(); private static ExecutorService newFixedThreadPoolWithQueueSize(int numThreads, int queueSize) { return new ThreadPoolExecutor(numThreads, numThreads, 5000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(queueSize, true), new ThreadPoolExecutor.CallerRunsPolicy()); } /** * FacebookFriendFeedProvider constructor - resolves FacebookUserInformationConfiguration from JVM 'facebook'. */ public FacebookFriendFeedProvider() { Config config = StreamsConfigurator.config.getConfig("facebook"); FacebookUserInformationConfiguration configuration; try { configuration = mapper.readValue(config.root().render(ConfigRenderOptions.concise()), FacebookUserInformationConfiguration.class); } catch (IOException ex) { ex.printStackTrace(); } } /** * FacebookFriendFeedProvider constructor - uses supplied FacebookUserInformationConfiguration. */ public FacebookFriendFeedProvider(FacebookUserstreamConfiguration config) { this.configuration = config; } /** * FacebookFriendFeedProvider constructor - output supplied Class. * @param klass Class */ public FacebookFriendFeedProvider(Class klass) { Config config = StreamsConfigurator.config.getConfig("facebook"); FacebookUserInformationConfiguration configuration; try { configuration = mapper.readValue(config.root().render(ConfigRenderOptions.concise()), FacebookUserInformationConfiguration.class); } catch (IOException ex) { ex.printStackTrace(); return; } this.klass = klass; } public FacebookFriendFeedProvider(FacebookUserstreamConfiguration config, Class klass) { this.configuration = config; this.klass = klass; } public Queue<StreamsDatum> getProviderQueue() { return this.providerQueue; } @Override public String getId() { return STREAMS_ID; } @Override public void startStream() { shutdownAndAwaitTermination(executor); running.set(true); } @Override public StreamsResultSet readCurrent() { StreamsResultSet current; synchronized (FacebookUserstreamProvider.class) { current = new StreamsResultSet(new ConcurrentLinkedQueue<>(providerQueue)); current.setCounter(new DatumStatusCounter()); current.getCounter().add(countersCurrent); countersTotal.add(countersCurrent); countersCurrent = new DatumStatusCounter(); providerQueue.clear(); } return current; } @Override public StreamsResultSet readNew(BigInteger sequence) { LOGGER.debug("{} readNew", STREAMS_ID); throw new NotImplementedException(); } @Override public StreamsResultSet readRange(DateTime start, DateTime end) { LOGGER.debug("{} readRange", STREAMS_ID); this.start = start; this.end = end; readCurrent(); return (StreamsResultSet)providerQueue.iterator(); } @Override public boolean isRunning() { return running.get(); } void shutdownAndAwaitTermination(ExecutorService pool) { pool.shutdown(); // Disable new tasks from being submitted try { // Wait a while for existing tasks to terminate if (!pool.awaitTermination(10, TimeUnit.SECONDS)) { pool.shutdownNow(); // Cancel currently executing tasks // Wait a while for tasks to respond to being cancelled if (!pool.awaitTermination(10, TimeUnit.SECONDS)) { System.err.println("Pool did not terminate"); } } } catch (InterruptedException ie) { // (Re-)Cancel if current thread also interrupted pool.shutdownNow(); // Preserve interrupt status Thread.currentThread().interrupt(); } } @Override public void prepare(Object configurationObject) { executor = newFixedThreadPoolWithQueueSize(5, 20); Objects.requireNonNull(providerQueue); Objects.requireNonNull(this.klass); Objects.requireNonNull(configuration.getOauth().getAppId()); Objects.requireNonNull(configuration.getOauth().getAppSecret()); Objects.requireNonNull(configuration.getOauth().getUserAccessToken()); Facebook client = getFacebookClient(); try { ResponseList<Friend> friendResponseList = client.friends().getFriends(); Paging<Friend> friendPaging; do { for ( Friend friend : friendResponseList ) { executor.submit(new FacebookFriendFeedTask(this, friend.getId())); } friendPaging = friendResponseList.getPaging(); friendResponseList = client.fetchNext(friendPaging); } while ( friendPaging != null && friendResponseList != null ); } catch (FacebookException ex) { ex.printStackTrace(); } } protected Facebook getFacebookClient() { ConfigurationBuilder cb = new ConfigurationBuilder(); cb.setDebugEnabled(true) .setOAuthAppId(configuration.getOauth().getAppId()) .setOAuthAppSecret(configuration.getOauth().getAppSecret()) .setOAuthAccessToken(configuration.getOauth().getUserAccessToken()) .setOAuthPermissions(ALL_PERMISSIONS) .setJSONStoreEnabled(true) .setClientVersion("v1.0"); FacebookFactory ff = new FacebookFactory(cb.build()); return ff.getInstance(); } @Override public void cleanUp() { shutdownAndAwaitTermination(executor); } private class FacebookFriendFeedTask implements Runnable { FacebookFriendFeedProvider provider; Facebook client; String id; public FacebookFriendFeedTask(FacebookFriendFeedProvider provider, String id) { this.provider = provider; this.id = id; } @Override public void run() { client = provider.getFacebookClient(); try { ResponseList<Post> postResponseList = client.getFeed(id); Paging<Post> postPaging; do { for (Post item : postResponseList) { String json = DataObjectFactory.getRawJSON(item); org.apache.streams.facebook.Post post = mapper.readValue(json, org.apache.streams.facebook.Post.class); try { lock.readLock().lock(); ComponentUtils.offerUntilSuccess(new StreamsDatum(post), providerQueue); countersCurrent.incrementAttempt(); } finally { lock.readLock().unlock(); } } postPaging = postResponseList.getPaging(); postResponseList = client.fetchNext(postPaging); } while ( postPaging != null && postResponseList != null ); } catch (Exception ex) { ex.printStackTrace(); } } } }