/* * 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.StreamsDatum; import org.apache.streams.core.StreamsProvider; import org.apache.streams.core.StreamsResultSet; import org.apache.streams.facebook.FacebookUserInformationConfiguration; import org.apache.streams.jackson.StreamsJacksonMapper; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Preconditions; import com.google.common.util.concurrent.MoreExecutors; 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.ResponseList; import facebook4j.User; import facebook4j.conf.ConfigurationBuilder; 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.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Queue; import java.util.concurrent.ArrayBlockingQueue; 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; public class FacebookUserInformationProvider implements StreamsProvider, Serializable { public static final String STREAMS_ID = "FacebookUserInformationProvider"; private static final Logger LOGGER = LoggerFactory.getLogger(FacebookUserInformationProvider.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 FacebookUserInformationConfiguration facebookUserInformationConfiguration; private Class klass; protected volatile Queue<StreamsDatum> providerQueue = new LinkedBlockingQueue<>(); public FacebookUserInformationConfiguration getConfig() { return facebookUserInformationConfiguration; } public void setConfig(FacebookUserInformationConfiguration config) { this.facebookUserInformationConfiguration = config; } protected Iterator<String[]> idsBatches; protected ExecutorService executor; protected DateTime start; protected DateTime end; protected final AtomicBoolean running = new AtomicBoolean(); private static ExecutorService newFixedThreadPoolWithQueueSize(int numThreads, int queueSize) { return new ThreadPoolExecutor(numThreads, numThreads, 5000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(queueSize, true), new ThreadPoolExecutor.CallerRunsPolicy()); } /** * FacebookUserInformationProvider constructor - resolves FacebookUserInformationConfiguration from JVM 'facebook'. */ public FacebookUserInformationProvider() { Config config = StreamsConfigurator.config.getConfig("facebook"); FacebookUserInformationConfiguration facebookUserInformationConfiguration; try { facebookUserInformationConfiguration = mapper.readValue(config.root().render(ConfigRenderOptions.concise()), FacebookUserInformationConfiguration.class); } catch (IOException ex) { ex.printStackTrace(); } } /** * FacebookUserInformationProvider constructor - uses supplie FacebookUserInformationConfiguration. * @param config */ public FacebookUserInformationProvider(FacebookUserInformationConfiguration config) { this.facebookUserInformationConfiguration = config; } public FacebookUserInformationProvider(Class klass) { Config config = StreamsConfigurator.config.getConfig("facebook"); FacebookUserInformationConfiguration facebookUserInformationConfiguration; try { facebookUserInformationConfiguration = mapper.readValue(config.root().render(ConfigRenderOptions.concise()), FacebookUserInformationConfiguration.class); } catch (IOException ex) { ex.printStackTrace(); return; } this.klass = klass; } public FacebookUserInformationProvider(FacebookUserInformationConfiguration config, Class klass) { this.facebookUserInformationConfiguration = config; this.klass = klass; } public Queue<StreamsDatum> getProviderQueue() { return this.providerQueue; } @Override public String getId() { return STREAMS_ID; } @Override public void startStream() { running.set(true); } @Override public StreamsResultSet readCurrent() { Preconditions.checkArgument(idsBatches.hasNext()); LOGGER.info("readCurrent"); Facebook client = getFacebookClient(); try { User me = client.users().getMe(); String json = mapper.writeValueAsString(me); providerQueue.add( new StreamsDatum(json, DateTime.now()) ); } catch (JsonProcessingException | FacebookException ex) { ex.printStackTrace(); } if (idsBatches.hasNext()) { while (idsBatches.hasNext()) { try { List<User> userList = client.users().getUsers(idsBatches.next()); for (User user : userList) { try { String json = mapper.writeValueAsString(user); providerQueue.add( new StreamsDatum(json, DateTime.now()) ); } catch (JsonProcessingException ex) { LOGGER.trace("JsonProcessingException", ex); } } } catch (FacebookException ex) { ex.printStackTrace(); } } } else { try { ResponseList<Friend> friendResponseList = client.friends().getFriends(); Paging<Friend> friendPaging; do { for ( Friend friend : friendResponseList ) { String json; try { json = mapper.writeValueAsString(friend); providerQueue.add( new StreamsDatum(json) ); } catch (JsonProcessingException ex) { LOGGER.trace("JsonProcessingException", ex); } } friendPaging = friendResponseList.getPaging(); friendResponseList = client.fetchNext(friendPaging); } while ( friendPaging != null && friendResponseList != null ); } catch (FacebookException ex) { ex.printStackTrace(); } } LOGGER.info("Finished. Cleaning up..."); LOGGER.info("Providing {} docs", providerQueue.size()); StreamsResultSet result = new StreamsResultSet(providerQueue); running.set(false); LOGGER.info("Exiting"); return result; } @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 = MoreExecutors.listeningDecorator(newFixedThreadPoolWithQueueSize(5, 20)); Objects.requireNonNull(providerQueue); Objects.requireNonNull(this.klass); Objects.requireNonNull(facebookUserInformationConfiguration.getOauth().getAppId()); Objects.requireNonNull(facebookUserInformationConfiguration.getOauth().getAppSecret()); Objects.requireNonNull(facebookUserInformationConfiguration.getOauth().getUserAccessToken()); Objects.requireNonNull(facebookUserInformationConfiguration.getInfo()); List<String> ids = new ArrayList<>(); List<String[]> idsBatches = new ArrayList<>(); for (String s : facebookUserInformationConfiguration.getInfo()) { if (s != null) { ids.add(s); if (ids.size() >= 100) { // add the batch idsBatches.add(ids.toArray(new String[ids.size()])); // reset the Ids ids = new ArrayList<>(); } } } if (ids.size() > 0) { idsBatches.add(ids.toArray(new String[ids.size()])); } this.idsBatches = idsBatches.iterator(); } protected Facebook getFacebookClient() { ConfigurationBuilder cb = new ConfigurationBuilder(); cb.setDebugEnabled(true) .setOAuthAppId(facebookUserInformationConfiguration.getOauth().getAppId()) .setOAuthAppSecret(facebookUserInformationConfiguration.getOauth().getAppSecret()) .setOAuthAccessToken(facebookUserInformationConfiguration.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); } }