/*******************************************************************************
* Copyright 2010 Universidade do Minho, Ricardo Vila�a and Francisco Cruz
*
* Licensed 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.ublog.benchmark.social.cassandra;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.ublog.utils.Pair;
import org.ublog.benchmark.BenchOperation;
import org.ublog.benchmark.ComplexBenchMarkClient;
import org.ublog.benchmark.operations.CollectionOperation;
import org.ublog.benchmark.operations.DataStoreOperation;
import org.ublog.benchmark.operations.GetOperation;
import org.ublog.benchmark.operations.GetRangeOperation;
import org.ublog.benchmark.operations.OperationListener;
import org.ublog.benchmark.operations.PutOperation;
import org.ublog.benchmark.social.Message;
import org.ublog.benchmark.social.SocialBenchmark;
import org.ublog.benchmark.social.UserService;
import org.ublog.benchmark.social.Utils;
public class StartFollowingOperation implements BenchOperation,
OperationListener {
private enum Phase {
UPDATE_FOLLOWERS, UPDATE_FOLLOWING, UPDATE_TIMELINE
}
private ComplexBenchMarkClient client;
private String userId, toStartUser;
private Phase currentPhase;
private Map<String, String> user;
private Map<String, String> toStart;
private boolean get;
private boolean finish;
private List<String> timeLine;
private List<Message> recentTweets;
public StartFollowingOperation(ComplexBenchMarkClient client,
String userId, String toStartUser) {
this.client = client;
this.userId = userId;
this.toStartUser = toStartUser;
this.currentPhase = Phase.UPDATE_FOLLOWING;
this.get = false;
this.finish = false;
this.user = null;
this.toStart = null;
this.recentTweets = null;
}
private String getTimelineId(Message tweet) {
return tweet.getId() + ":" + tweet.getDate().toString();
}
@SuppressWarnings("unchecked")
@Override
public void handleFinishedOperation(DataStoreOperation op) {
switch (this.currentPhase) {
case UPDATE_FOLLOWING:
if (this.user == null) {
this.user = (Map<String, String>) ((GetOperation) op)
.getResult();
} else
this.currentPhase = Phase.UPDATE_FOLLOWERS;
break;
case UPDATE_FOLLOWERS:
if (this.toStart == null) {
this.toStart = (Map<String, String>) ((GetOperation) op)
.getResult();
} else
this.currentPhase = Phase.UPDATE_TIMELINE;
break;
case UPDATE_TIMELINE:
if (this.timeLine == null) {
this.timeLine = (List<String>) ((GetOperation) op).getResult();
if (this.timeLine == null)
this.timeLine = new ArrayList<String>();
} else if (this.recentTweets == null) {
this.recentTweets = new ArrayList<Message>();
for (Map<String, String> tweetMap : ((Set<Map<String, String>>) ((GetRangeOperation) op)
.getResult())) {
if (!tweetMap.isEmpty()) {
this.recentTweets.add(Utils.toTweet(tweetMap));
}
}
} else
this.client.handleFinishedComplexOperation(this);
break;
}
}
@Override
public String getName() {
return "twitter:startFollowing";
}
@SuppressWarnings("unchecked")
@Override
public CollectionOperation getNextDBOperation()
throws UnsupportedEncodingException {
CollectionOperation res = null;
switch (this.currentPhase) {
case UPDATE_FOLLOWING:
if (!this.get) {
// CASSANDRA
List<byte[]> columns = new ArrayList<byte[]>();
columns.add("following".getBytes("UTF-8"));
// FINISHED
res = new GetOperation(client, SocialBenchmark.userTable,
this.userId, columns);
this.get = true;
} else {
Set<String> following = Utils.asSet(user
.get(UserService.FOLLOWING));
following.add(toStartUser);
this.user.put(UserService.FOLLOWING, Utils.toString(following));
res = new PutOperation(client, SocialBenchmark.userTable,
this.userId, this.user, null);
this.get = false;
}
break;
case UPDATE_FOLLOWERS:
if (!this.get) {
// CASSANDRA
List<byte[]> columns = new ArrayList<byte[]>();
columns.add("followers".getBytes("UTF-8"));
columns.add("lastTweet".getBytes("UTF-8"));
// FINISHED
res = new GetOperation(client, SocialBenchmark.userTable,
this.toStartUser, columns);
this.get = true;
} else {
Set<String> followers = Utils.asSet(user
.get(UserService.FOLLOWERS));
followers.add(userId);
this.toStart.put(UserService.FOLLOWERS,
Utils.toString(followers));
res = new PutOperation(client, SocialBenchmark.userTable,
this.toStartUser, this.toStart, null);
this.get = false;
}
break;
case UPDATE_TIMELINE:
if (!this.get) {
this.get = true;
this.timeLine = null;
res = new GetOperation(client,
SocialBenchmark.friendsTimeLineTable, this.userId, 250);
} else {
if (this.recentTweets == null) {
int lastTweet = Integer.valueOf(this.toStart
.get(UserService.LAST_TWEET));
String min = this.toStartUser
+ "-"
+ Utils.getTweetPadding(Math.max(1,
(lastTweet - 20 + 1)));
String max = this.toStartUser + "-"
+ Utils.getTweetPadding(lastTweet);
res = new GetRangeOperation(client,
SocialBenchmark.tweetsTable, min, max);
} else {
List<Pair<String, String>> toAddToTimeline = new ArrayList<Pair<String, String>>();
int timelineIdx = 0;
long timelineDate = 0;
for (Message tweet : recentTweets) {
long date = tweet.getDate().timestamp();
String timelineId = getTimelineId(tweet);
if (this.timeLine.contains(timelineId)) {
continue;
}
while (timelineIdx < this.timeLine.size()
&& timelineIdx < Utils.MAX_MESSAGES_IN_TIMELINE) {
String id = this.timeLine.get(timelineIdx);
String[] split = id.split(":");
timelineDate = new Long(java.util.UUID.fromString(
split[1]).timestamp());
if (timelineDate > date) {
// CASSANDRA
toAddToTimeline
.add(new Pair<String, String>(tweet
.getDate().toString(),
timelineId));
// FINISHED
timelineIdx++;
break;
}
timelineIdx++;
}
if (timelineIdx == this.timeLine.size()) {
// CASSANDRA
toAddToTimeline.add(new Pair<String, String>(tweet
.getDate().toString(), timelineId));
// FINISHED
}
if (timelineIdx == Utils.MAX_MESSAGES_IN_TIMELINE) {
break;
}
}
res = new PutOperation(client,
SocialBenchmark.friendsTimeLineTable, this.userId,
toAddToTimeline, null);
this.finish = true;
}
}
break;
}
res.addListener(this);
return res;
}
@Override
public boolean hasMoreDBOperations() {
return !this.finish;
}
@Override
public String toString() {
return "StartFollowingOperation [toStartUser=" + toStartUser
+ ", userId=" + userId + "]";
}
@Override
public boolean isReadOnly() {
return false;
}
@Override
public boolean isInit() {
// TODO Auto-generated method stub
return false;
}
@Override
public void setInit(boolean init) {
// TODO Auto-generated method stub
}
}