/*
It is a application for event distribution to event n-consumers with m-sources.
Copyright (C) 2010 "Imran M Yousuf <imran@smartitengineering.com>"
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or any later
version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.smartitengineering.event.hub.spi.hbase;
import com.google.inject.Inject;
import com.smartitengineering.dao.common.CommonReadDao;
import com.smartitengineering.dao.common.CommonWriteDao;
import com.smartitengineering.dao.common.queryparam.MatchMode;
import com.smartitengineering.dao.common.queryparam.QueryParameter;
import com.smartitengineering.dao.common.queryparam.QueryParameterFactory;
import com.smartitengineering.dao.impl.hbase.spi.RowCellIncrementor;
import com.smartitengineering.event.hub.api.Channel;
import com.smartitengineering.event.hub.api.Event;
import com.smartitengineering.event.hub.spi.HubPersistentStorer;
import com.smartitengineering.event.hub.spi.hbase.persistents.RowAutoIdIndex;
import com.smartitengineering.event.hub.spi.hbase.persistents.ChannelAdapterHelper;
import com.smartitengineering.event.hub.spi.hbase.persistents.EventAdapterHelper;
import com.smartitengineering.event.hub.spi.hbase.persistents.EventId;
import com.smartitengineering.event.hub.spi.hbase.persistents.EventUUID;
import com.smartitengineering.event.hub.spi.hbase.persistents.PersistentChannel;
import com.smartitengineering.event.hub.spi.hbase.persistents.PersistentEvent;
import com.smartitengineering.event.hub.spi.hbase.persistents.ReverseIdIndex;
import com.smartitengineering.util.bean.adapter.GenericAdapter;
import com.smartitengineering.util.bean.adapter.GenericAdapterImpl;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.hadoop.hbase.util.Bytes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author imyousuf
*/
public class HubPersistentStorerImpl implements HubPersistentStorer {
private static final String CHANNELS_ROW_ID_N_PREFIX = "channels";
private static final String EVENTS_ROW_ID_N_PREFIX = "events";
private static final PersistentChannel[] EMPTY_CHANNEL_ARRAY = new PersistentChannel[0];
private static final PersistentEvent[] EMPTY_EVENT_ARRAY = new PersistentEvent[0];
public static final int MAX_LENGTH = String.valueOf(Long.MAX_VALUE).length();
@Inject
protected CommonWriteDao<PersistentChannel> channelWrtDao;
@Inject
protected CommonWriteDao<PersistentEvent> eventWrtDao;
@Inject
protected CommonWriteDao<EventUUID> eventUUIDWrtDao;
@Inject
protected CommonWriteDao<RowAutoIdIndex> autoIdWrtDao;
@Inject
protected CommonWriteDao<ReverseIdIndex> reverseIdIndexWrtDao;
@Inject
protected CommonReadDao<PersistentChannel, Long> channelRdDao;
@Inject
protected CommonReadDao<PersistentEvent, EventId> eventRdDao;
@Inject
protected CommonReadDao<EventUUID, String> eventUUIDRdDao;
@Inject
protected CommonReadDao<RowAutoIdIndex, String> autoIdRdDao;
@Inject
protected CommonReadDao<ReverseIdIndex, String> reverseIdIndexRdDao;
@Inject
protected RowCellIncrementor<Channel, RowAutoIdIndex, String> idIncrementor;
@Inject
protected RowCellIncrementor<ReverseIdIndex, RowAutoIdIndex, String> reverseIdIncrementor;
protected boolean channelAutoIdInitialized = false;
protected boolean eventAutoIdInitialized = false;
protected final GenericAdapter<Channel, PersistentChannel> channelAdapter;
protected final GenericAdapter<Event, PersistentEvent> eventAdapter;
protected Logger logger = LoggerFactory.getLogger(getClass());
public HubPersistentStorerImpl() {
GenericAdapterImpl<Channel, PersistentChannel> lChannelAdapter =
new GenericAdapterImpl<Channel, PersistentChannel>();
lChannelAdapter.setHelper(new ChannelAdapterHelper());
this.channelAdapter = lChannelAdapter;
GenericAdapterImpl<Event, PersistentEvent> lEventAdapter = new GenericAdapterImpl<Event, PersistentEvent>();
lEventAdapter.setHelper(new EventAdapterHelper());
this.eventAdapter = lEventAdapter;
}
@Override
public void create(Channel channel) {
checkAndInitializeAutoId();
if (!channelAutoIdInitialized) {
throw new IllegalStateException("Channel ID could not be generated!");
}
if (channel == null) {
return;
}
PersistentChannel pChannel = channelAdapter.convert(channel);
if (!pChannel.isValid()) {
throw new IllegalStateException("Channel not in valid state!");
}
Channel eChannel = getChannel(pChannel.getName());
if (eChannel != null) {
throw new IllegalStateException("Channel already exists!");
}
try {
long channelId = idIncrementor.incrementAndGet(CHANNELS_ROW_ID_N_PREFIX, -1);
long channelReverseId = reverseIdIncrementor.incrementAndGet(CHANNELS_ROW_ID_N_PREFIX, 1);
pChannel.setId(channelId);
final Date date = new Date();
pChannel.setCreationDateTime(date);
pChannel.setLastModifiedDateTime(date);
RowAutoIdIndex channelEventId = new RowAutoIdIndex();
channelEventId.setAutoIdValue(Long.MAX_VALUE);
channelEventId.setId(getChannelIdIndexName(pChannel.getName()));
channelEventId.setReverseId(String.valueOf(channelId));
ReverseIdIndex idIndex = new ReverseIdIndex();
idIndex.setId(getChannelIdIndexName(String.valueOf(channelReverseId)));
idIndex.setReverseId(String.valueOf(channelId));
autoIdWrtDao.save(channelEventId);
channelWrtDao.save(pChannel);
reverseIdIndexWrtDao.save(idIndex);
}
catch (RuntimeException ex) {
logger.error("Could not create channel or its events' auto id generator!", ex);
throw ex;
}
}
@Override
public void update(Channel channel) {
if (channel == null) {
return;
}
checkAndInitializeAutoId();
PersistentChannel pChannel = getMergedPersistentChannel(channel);
if (!pChannel.isValid()) {
throw new IllegalStateException("Channel not in valid state!");
}
try {
pChannel.setLastModifiedDateTime(new Date());
channelWrtDao.update(pChannel);
}
catch (RuntimeException ex) {
logger.error("Could not update channel!", ex);
throw ex;
}
}
@Override
public void delete(Channel channel) {
if (channel == null) {
return;
}
checkAndInitializeAutoId();
PersistentChannel pChannel = getMergedPersistentChannel(channel);
if (!pChannel.isValid()) {
throw new IllegalStateException("Channel not in valid state!");
}
try {
channelWrtDao.delete(pChannel);
}
catch (RuntimeException ex) {
logger.error("Could not update channel!", ex);
throw ex;
}
}
@Override
public Channel getChannel(String channelName) {
if (StringUtils.isBlank(channelName)) {
return null;
}
return channelAdapter.convertInversely(getPersistentChannel(channelName));
}
@Override
public Collection<Channel> getChannels(int startIndex, int count) {
if (count == 0) {
return Collections.emptyList();
}
final QueryParameter<Integer> maxResultsParam = QueryParameterFactory.getMaxResultsParam(Math.abs(count));
if (count < 0) {
final QueryParameter param;
long index = Long.MAX_VALUE - startIndex;
if (logger.isDebugEnabled()) {
logger.debug("Reverse Index value " + index);
}
param = QueryParameterFactory.getGreaterThanPropertyParam("id", Bytes.toBytes(index));
final List<PersistentChannel> list = channelRdDao.getList(param, maxResultsParam);
if (logger.isDebugEnabled()) {
logger.debug("Result " + list);
}
return channelAdapter.convertInversely(list.toArray(EMPTY_CHANNEL_ARRAY));
}
else {
final QueryParameter param = QueryParameterFactory.getGreaterThanPropertyParam("id", Bytes.toBytes(getChannelIdIndexName(String.
valueOf(startIndex))));
List<ReverseIdIndex> reverseIndexes = reverseIdIndexRdDao.getList(param, maxResultsParam);
List<Long> ids = new ArrayList<Long>(reverseIndexes.size());
for (ReverseIdIndex index : reverseIndexes) {
final long longVal = NumberUtils.toLong(index.getReverseId());
if (longVal > -1) {
ids.add(longVal);
}
}
final Set<PersistentChannel> byIds = channelRdDao.getByIds(ids);
if (logger.isDebugEnabled()) {
logger.debug("Result " + reverseIndexes + " - " + byIds);
}
return channelAdapter.convertInversely(byIds.toArray(EMPTY_CHANNEL_ARRAY));
}
}
@Override
public Event create(Channel channel, Event event) {
PersistentEvent persistentEvent = eventAdapter.convert(event);
if (persistentEvent != null && channel != null) {
String eventsId = EVENTS_ROW_ID_N_PREFIX;
long placheholderId = idIncrementor.incrementAndGet(eventsId, -1);
long revPlacheholderId = reverseIdIncrementor.incrementAndGet(eventsId, 1);
persistentEvent.setPlaceholderId(String.valueOf(placheholderId));
persistentEvent.setChannelId(channel.getName());
persistentEvent.getId().setEventIdForChannel(placheholderId);
persistentEvent.setCreationDateTime(new Date());
if (StringUtils.isBlank(persistentEvent.getUuid())) {
UUID uuid = UUID.randomUUID();
persistentEvent.setUuid(uuid.toString());
}
else {
Event possDupEvent = getEventByUUID(persistentEvent.getUuid());
if (possDupEvent != null) {
throw new IllegalArgumentException("Duplication event!");
}
}
EventUUID eUuid = new EventUUID();
eUuid.setEventId(persistentEvent.getId());
eUuid.setId(persistentEvent.getUuid());
ReverseIdIndex reverseIdIndex = new ReverseIdIndex();
reverseIdIndex.setReverseId(persistentEvent.getId().toString());
reverseIdIndex.setId(
new StringBuilder().append(leftPadNumberWithZero(revPlacheholderId)).append(':').
append(channel.getName()).toString());
eventWrtDao.save(persistentEvent);
reverseIdIndexWrtDao.save(reverseIdIndex);
eventUUIDWrtDao.save(eUuid);
final Event convertInversely = eventAdapter.convertInversely(persistentEvent);
if (logger.isDebugEnabled()) {
logger.debug("Event's ID, UUID and PlaceholderID: " + persistentEvent.getId().toString() + " " + convertInversely.
getUniversallyUniqueID() + " " + convertInversely.getPlaceholderId());
}
return convertInversely;
}
return null;
}
@Override
public void delete(Event event) {
if (event == null) {
return;
}
final PersistentEvent persistentEvent = getPersistentEvent(NumberUtils.toLong(event.getPlaceholderId()));
if (persistentEvent == null) {
return;
}
eventWrtDao.delete(persistentEvent);
}
@Override
public Event getEvent(String placeholderId) {
final PersistentEvent persistentEvent = getPersistentEvent(NumberUtils.toLong(placeholderId));
final Event convertInversely = eventAdapter.convertInversely(persistentEvent);
if (logger.isDebugEnabled()) {
if (persistentEvent != null && convertInversely != null) {
logger.debug("Event's ID, UUID and PlaceholderID: " + persistentEvent.getId().toString() + " " + convertInversely.
getUniversallyUniqueID() + " " + convertInversely.getPlaceholderId());
}
else {
logger.info("EVENT IS NULL!");
}
}
return convertInversely;
}
@Override
public Event getEventByUUID(String uuid) {
if (StringUtils.isBlank(uuid)) {
return null;
}
EventUUID eUuid = eventUUIDRdDao.getById(uuid);
if (eUuid != null) {
PersistentEvent event = eventRdDao.getById(eUuid.getEventId());
if (event != null) {
return eventAdapter.convertInversely(event);
}
}
return null;
}
@Override
public LinkedHashSet<Event> getEvents(String placeholderId, final String channelId, int count) {
if (count == 0) {
return new LinkedHashSet<Event>();
}
final QueryParameter<Integer> maxResultsParam = QueryParameterFactory.getMaxResultsParam(Math.abs(count));
final String eventChannelId;
long eventId = NumberUtils.toLong(placeholderId);
if (eventId > -1) {
if (StringUtils.isNotBlank(placeholderId)) {
PersistentEvent pEvent = getPersistentEvent(eventId);
if (pEvent != null) {
eventChannelId = pEvent.getChannelId();
}
else {
eventChannelId = "";
}
}
else {
eventChannelId = channelId;
}
}
else {
placeholderId = null;
eventChannelId = "";
if(count > 0) {
count = -1 * count;
}
}
if (count < 0) {
final List<QueryParameter> params = new ArrayList<QueryParameter>();
params.add(maxResultsParam);
if (StringUtils.isNotBlank(placeholderId)) {
final StringBuilder searchId = new StringBuilder(leftPadNumberWithZero(NumberUtils.toLong(placeholderId))).
append(':');
searchId.append(eventChannelId);
if (logger.isDebugEnabled()) {
logger.debug("Event Id to search greater or smaller than: " + searchId);
}
params.add(QueryParameterFactory.getGreaterThanPropertyParam("id", Bytes.toBytes(searchId.toString())));
}
if (StringUtils.isNotBlank(channelId)) {
String toString;
toString = new StringBuilder("[0-9]+").append(':').append(channelId.toLowerCase()).toString();
if (logger.isDebugEnabled()) {
logger.debug("Channel to search with id at end " + toString);
}
params.add(QueryParameterFactory.getStringLikePropertyParam("id", toString, MatchMode.END));
}
logger.debug("Doing event straight search!");
final List<PersistentEvent> list = eventRdDao.getList(params);
if (logger.isDebugEnabled()) {
logger.debug("Straight event search completed!");
for (PersistentEvent pEvent : list) {
logger.debug("EVENT " + pEvent.getId().toString());
}
}
return new LinkedHashSet<Event>(eventAdapter.convertInversely(
list.toArray(EMPTY_EVENT_ARRAY)));
}
else {
final List<QueryParameter> params = new ArrayList<QueryParameter>();
params.add(maxResultsParam);
if (StringUtils.isNotBlank(placeholderId)) {
final long toLong = NumberUtils.toLong(placeholderId);
final long reversePlaceholderId = Long.MAX_VALUE - toLong;
params.add(QueryParameterFactory.getGreaterThanPropertyParam("id", Bytes.toBytes(new StringBuilder(leftPadNumberWithZero(
reversePlaceholderId)).append(':').append(eventChannelId).toString())));
}
final String toString;
if (StringUtils.isNotBlank(channelId)) {
toString = new StringBuilder("[0-9]+").append("\\:").append(channelId.toLowerCase()).toString();
}
else {
toString = new StringBuilder("[0-9]+").append("\\:").append(".+").toString();
}
if (logger.isDebugEnabled()) {
logger.debug("End pattern to test for! " + toString);
}
params.add(QueryParameterFactory.getStringLikePropertyParam("id", toString, MatchMode.END));
logger.debug("Doing reverse event search!");
List<ReverseIdIndex> indexes = reverseIdIndexRdDao.getList(params);
List<EventId> eventIds = new ArrayList<EventId>(indexes.size());
for (ReverseIdIndex index : indexes) {
if (logger.isDebugEnabled()) {
logger.debug("Reverse ID: " + index.getReverseId());
}
eventIds.add(EventId.fromString(index.getReverseId()));
}
Collections.reverse(eventIds);
final Set<PersistentEvent> byIds = eventRdDao.getByIds(eventIds);
logger.debug("Reverse event search completed!");
return new LinkedHashSet<Event>(eventAdapter.convertInversely(byIds.toArray(
EMPTY_EVENT_ARRAY)));
}
}
protected String getChannelIdIndexName(String channelName) {
return new StringBuilder(CHANNELS_ROW_ID_N_PREFIX).append(':').append(channelName).toString();
}
protected String getEventIdIndexName(String eventId) {
return new StringBuilder(EVENTS_ROW_ID_N_PREFIX).append(':').append(eventId).toString();
}
protected void checkAndInitializeAutoId() throws RuntimeException {
if (!channelAutoIdInitialized) {
channelAutoIdInitialized = checkAndInitializeAutoId(CHANNELS_ROW_ID_N_PREFIX);
}
if (!eventAutoIdInitialized) {
eventAutoIdInitialized = checkAndInitializeAutoId(EVENTS_ROW_ID_N_PREFIX);
}
}
protected boolean checkAndInitializeAutoId(String autoId) throws RuntimeException {
RowAutoIdIndex id = autoIdRdDao.getById(autoId);
if (id == null) {
id = new RowAutoIdIndex();
id.setAutoIdValue(Long.MAX_VALUE);
id.setReverseAutoIdValue(0l);
id.setId(autoId);
try {
autoIdWrtDao.save(id);
return true;
}
catch (RuntimeException ex) {
logger.error("Could not initialize channel auto id!", ex);
throw ex;
}
}
else {
return true;
}
}
protected String leftPadNumberWithZero(long revPlacheholderId) {
return StringUtils.leftPad(String.valueOf(revPlacheholderId), MAX_LENGTH, '0');
}
protected PersistentChannel getMergedPersistentChannel(Channel channel) {
if (channel == null || StringUtils.isBlank(channel.getName())) {
return null;
}
PersistentChannel persistentChannel = getPersistentChannel(channel.getName());
if (logger.isDebugEnabled()) {
logger.debug("Persistent channel found is " + persistentChannel);
logger.debug("Persistent channel ID is " + persistentChannel.getId());
}
Map.Entry<Channel, PersistentChannel> entry;
entry = new SimpleEntry<Channel, PersistentChannel>(channel, persistentChannel);
channelAdapter.merge(entry);
if (logger.isDebugEnabled()) {
logger.debug("Persistent channel ID is " + persistentChannel.getId());
}
return persistentChannel;
}
protected PersistentChannel getPersistentChannel(String channelName) {
if (StringUtils.isBlank(channelName)) {
return null;
}
final String channelIdIndexName = getChannelIdIndexName(channelName.toLowerCase());
if (logger.isDebugEnabled()) {
logger.debug("Getting channel with name in reverse lookup table " + channelIdIndexName);
}
RowAutoIdIndex idIndex = autoIdRdDao.getById(channelIdIndexName);
if (idIndex != null) {
logger.debug("Found ID in channel name index");
final long channelId = NumberUtils.toLong(idIndex.getReverseId());
if (logger.isDebugEnabled()) {
logger.debug("Found reverse index for channel " + channelId);
}
PersistentChannel persistentChannel = channelRdDao.getById(channelId);
return persistentChannel;
}
else {
logger.debug("Did not found ID in channel name index");
PersistentChannel persistentChannel = channelRdDao.getSingle(QueryParameterFactory.getStringLikePropertyParam(
PersistentChannel.NAME, channelName.toLowerCase(), MatchMode.EXACT));
return persistentChannel;
}
}
protected PersistentEvent getMergedPersistentEvent(Event event) {
if (event == null) {
return null;
}
PersistentEvent persistentEvent = getPersistentEvent(NumberUtils.toLong(event.getPlaceholderId()));
Map.Entry<Event, PersistentEvent> entry;
entry = new SimpleEntry<Event, PersistentEvent>(event, persistentEvent);
eventAdapter.merge(entry);
return persistentEvent;
}
protected PersistentEvent getPersistentEvent(long placeholderId) {
if (placeholderId <= 0) {
logger.debug("Invalid place holder id!");
return null;
}
PersistentEvent persistentEvent =
eventRdDao.getSingle(
QueryParameterFactory.getStringLikePropertyParam("id", leftPadNumberWithZero(placeholderId), MatchMode.START));
return persistentEvent;
}
}