/* * Copyright 2013, Matt Brozowski and Eric Evans * * 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.opennms.twissandra.persistence.cassandra.internal; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.UUID; import org.opennms.twissandra.api.Tweet; import org.opennms.twissandra.api.TweetRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.datastax.driver.core.Cluster; import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.Row; import com.datastax.driver.core.Session; import com.datastax.driver.core.utils.UUIDs; public class CassandraTweetRepository implements TweetRepository { private static final String PUBLIC_USERLINE_KEY = "!PUBLIC!"; private static final Logger LOG = LoggerFactory.getLogger(CassandraTweetRepository.class); private final String m_cassandraHost; private final int m_cassandraPort; private final String m_keyspaceName; private final Cluster cluster; private final Session session; public CassandraTweetRepository(String cassandraHost, int cassandraPort, String keyspaceName) { m_cassandraHost = cassandraHost; m_cassandraPort = cassandraPort; m_keyspaceName = keyspaceName; LOG.info("Connecting to {}:{}...", cassandraHost, cassandraPort); cluster = Cluster.builder().withPort(m_cassandraPort).addContactPoint(m_cassandraHost).build(); session = cluster.connect(m_keyspaceName); LOG.info("Connected."); } public String getPassword(String username) { ResultSet queryResult = execute("SELECT password FROM users WHERE username = '%s'", username); Row row = getOneRow(queryResult); return row != null ? row.getString("password") : null; } public List<String> getFriends(String username) { ResultSet queryResult = execute("SELECT followed FROM following WHERE username = '%s'", username); List<String> friends = new ArrayList<String>(); for (Row row : queryResult) friends.add(row.getString("followed")); return friends; } public List<String> getFollowers(String username) { ResultSet queryResult = execute("SELECT following FROM followers WHERE username = '%s'", username); List<String> followers = new ArrayList<String>(); for (Row row : queryResult) followers.add(row.getString("following")); return followers; } /** {@inheritDoc} */ public List<Tweet> getUserline(String username, Date start, int limit) { ResultSet queryResult = execute( "SELECT tweetid, body FROM userline WHERE username = '%s' AND tweetid < maxTimeuuid('%d') ORDER BY tweetid DESC LIMIT %d", username, start.getTime(), limit); List<Tweet> tweets = new ArrayList<Tweet>(); for (Row row : queryResult) { UUID id = row.getUUID("tweetid"); tweets.add(new Tweet(username, row.getString("body"), id, fromUUID1(id))); } return tweets; } /** {@inheritDoc} */ public List<Tweet> getTimeline(String username, Date start, int limit) { ResultSet queryResult = execute( "SELECT tweetid, posted_by, body FROM timeline WHERE username = '%s' AND tweetid < maxTimeuuid('%d') ORDER BY tweetid DESC LIMIT %d", username, start.getTime(), limit); List<Tweet> tweets = new ArrayList<Tweet>(); for (Row row : queryResult) { UUID id = row.getUUID("tweetid"); tweets.add(new Tweet(row.getString("posted_by"), row.getString("body"), id, fromUUID1(id))); } return tweets; } public List<Tweet> getTweets(Date start, int limit) { return getTimeline(PUBLIC_USERLINE_KEY, start, limit); } public Tweet getTweet(UUID id) { Row row = getOneRow(execute("SELECT username, body FROM tweets WHERE tweetid = %s", id.toString())); return row != null ? new Tweet(row.getString("username"), row.getString("body"), id, fromUUID1(id)) : null; } public void saveUser(String username, String password) { execute("INSERT INTO users (username, password) VALUES ('%s', '%s')", username , password); } public Tweet saveTweet(String username, String body) { UUID id = UUIDs.timeBased(); // Create the tweet in the tweets cf execute("INSERT INTO tweets (tweetid, username, body) VALUES (%s, '%s', '%s')", id.toString(), username, body); // Store the tweet in this users userline execute("INSERT INTO userline (username, tweetid, body) VALUES ('%s', %s, '%s')", username, id.toString(), body); // Store the tweet in this users timeline execute("INSERT INTO timeline (username, tweetid, posted_by, body) VALUES ('%s', %s, '%s', '%s')", username, id.toString(), username, body); // Store the tweet in the public timeline execute("INSERT INTO timeline (username, tweetid, posted_by, body) VALUES ('%s', %s, '%s', '%s')", PUBLIC_USERLINE_KEY, id.toString(), username, body); // Insert the tweet into follower timelines for (String follower : getFollowers(username)) { execute("INSERT INTO timeline (username, tweetid, posted_by, body) VALUES ('%s', %s, '%s', '%s')", follower, id.toString(), username, body); } return new Tweet(username, body, id, fromUUID1(id)); } public void addFriend(String username, String friend) { if (username.equals(friend)) { LOG.warn("Attempted to self-friend {} (no self-loving here, please).", username); return; } execute("INSERT INTO following (username, followed) VALUES ('%s', '%s')", username, friend); execute("INSERT INTO followers (username, following) VALUES ('%s', '%s')", friend, username); } public void removeFriend(String username, String friend) { execute("DELETE FROM following WHERE username = '%s' AND followed = '%s'", username, friend); execute("DELETE FROM followers WHERE username = '%s' AND following = '%s'", friend, username); } private Row getOneRow(ResultSet result) { Row row = result.one(); if (!result.isExhausted()) throw new RuntimeException("ResultSet instance contained more than one row!"); return row; } private ResultSet execute(String query, Object...parms) { String cql = String.format(query, parms); LOG.debug("Executing CQL: {}", cql); return session.execute(cql); } private Date fromUUID1(UUID uuid) { assert uuid.version() == 1; return new Date((uuid.timestamp() / 10000) + -12219292800000L); // -12219292800000L is the start epoch } }