/*
* 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.FacebookConfiguration;
import org.apache.streams.facebook.IdConfig;
import org.apache.streams.jackson.StreamsJacksonMapper;
import org.apache.streams.util.ComponentUtils;
import org.apache.streams.util.SerializationUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.typesafe.config.ConfigRenderOptions;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Abstract {@link org.apache.streams.core.StreamsProvider} for facebook.
*/
public abstract class FacebookProvider implements StreamsProvider {
private static final String STREAMS_ID = "FacebookProvider";
private static final Logger LOGGER = LoggerFactory.getLogger(FacebookProvider.class);
private static final ObjectMapper MAPPER = StreamsJacksonMapper.getInstance();
private static final int MAX_BATCH_SIZE = 2000;
protected FacebookConfiguration configuration;
protected BlockingQueue<StreamsDatum> datums;
private AtomicBoolean isComplete;
private ListeningExecutorService executor;
List<ListenableFuture<Object>> futures = new ArrayList<>();
private FacebookDataCollector dataCollector;
/**
* FacebookProvider constructor - resolves FacebookConfiguration from JVM 'facebook'.
*/
public FacebookProvider() {
try {
this.configuration = MAPPER.readValue(StreamsConfigurator.config.getConfig("facebook").root().render(ConfigRenderOptions.concise()), FacebookConfiguration.class);
} catch (IOException ioe) {
LOGGER.error("Exception trying to read default config : {}", ioe);
}
}
/**
* FacebookProvider constructor - uses supplied FacebookConfiguration.
*/
public FacebookProvider(FacebookConfiguration configuration) {
this.configuration = SerializationUtil.cloneBySerialization(configuration);
}
@Override
public String getId() {
return STREAMS_ID;
}
@Override
public void startStream() {
ListenableFuture future = executor.submit(getDataCollector());
futures.add(future);
executor.shutdown();
}
protected abstract FacebookDataCollector getDataCollector();
@Override
public StreamsResultSet readCurrent() {
int batchSize = 0;
BlockingQueue<StreamsDatum> batch = new LinkedBlockingQueue<>();
while (!this.datums.isEmpty() && batchSize < MAX_BATCH_SIZE) {
ComponentUtils.offerUntilSuccess(ComponentUtils.pollWhileNotEmpty(this.datums), batch);
++batchSize;
}
return new StreamsResultSet(batch);
}
@Override
public StreamsResultSet readNew(BigInteger sequence) {
return null;
}
@Override
public StreamsResultSet readRange(DateTime start, DateTime end) {
return null;
}
@Override
public void prepare(Object configurationObject) {
this.datums = new LinkedBlockingQueue<>();
this.isComplete = new AtomicBoolean(false);
this.executor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(1));
}
@Override
public void cleanUp() {
ComponentUtils.shutdownExecutor(executor, 5, 5);
executor = null;
}
/**
* Overrides the ids and addedAfter time in the configuration.
* @param idsToAfterDate idsToAfterDate
*/
public void overrideIds(Map<String, DateTime> idsToAfterDate) {
Set<IdConfig> ids = new HashSet<>();
for (String id : idsToAfterDate.keySet()) {
IdConfig idConfig = new IdConfig();
idConfig.setId(id);
idConfig.setAfterDate(idsToAfterDate.get(id));
ids.add(idConfig);
}
this.configuration.setIds(ids);
}
@Override
public boolean isRunning() {
if (datums.isEmpty() && executor.isTerminated() && Futures.allAsList(futures).isDone()) {
LOGGER.info("Completed");
isComplete.set(true);
LOGGER.info("Exiting");
}
return !isComplete.get();
}
}