/* * Copyright 2019 Arcus Project * * 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 com.iris.core.dao.cassandra; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Spliterator; import java.util.Spliterators; import java.util.UUID; import java.util.stream.Stream; import java.util.stream.StreamSupport; import com.codahale.metrics.Timer; import com.codahale.metrics.Timer.Context; import com.datastax.driver.core.BoundStatement; import com.datastax.driver.core.PreparedStatement; import com.datastax.driver.core.Row; import com.datastax.driver.core.Session; import com.google.common.collect.Iterators; import com.google.inject.Inject; import com.google.inject.Singleton; import com.iris.core.dao.AccountDAO; import com.iris.core.dao.metrics.DaoMetrics; import com.iris.messages.capability.AccountCapability; import com.iris.messages.capability.Capability; import com.iris.messages.model.Account; import com.iris.messages.model.ServiceLevel; import com.iris.platform.PagedResults; import com.iris.platform.model.ModelEntity; import com.iris.util.IrisUUID; @Singleton public class AccountDAOImpl extends BaseCassandraCRUDDao<UUID, Account> implements AccountDAO { private static final String TABLE = "account"; private static final Timer listAccountsTimer = DaoMetrics.readTimer(AccountDAO.class, "listAccounts"); private static final Timer streamAllTimer = DaoMetrics.readTimer(AccountDAO.class, "streamAll"); static class AccountEntityColumns { final static String STATE = "state"; final static String BILLABLE = "billable"; final static String TAX_EXEMPT = "taxExempt"; final static String BILLING_FIRST_NAME = "billingFirstName"; final static String BILLING_LAST_NAME = "billingLastName"; final static String BILLING_CC_TYPE = "billingCCType"; final static String BILLING_CC_LAST4 = "billingCCLast4"; final static String BILLING_STREET1 = "billingStreet1"; final static String BILLING_STREET2 = "billingStreet2"; final static String BILLING_CITY = "billingCity"; final static String BILLING_STATE = "billingState"; final static String BILLING_ZIP = "billingZip"; final static String PLACE_IDS = "placeids"; final static String SUBSCRIPTION_ID_MAP = "subscriptionids"; final static String BILLING_ZIP_PLUS4 = "billingZipPlusFour"; final static String OWNER = "owner"; final static String TRIAL_END = "trialEnd"; }; private static final String[] COLUMN_ORDER = { AccountEntityColumns.STATE, AccountEntityColumns.BILLABLE, AccountEntityColumns.TAX_EXEMPT, AccountEntityColumns.BILLING_FIRST_NAME, AccountEntityColumns.BILLING_LAST_NAME, AccountEntityColumns.BILLING_CC_TYPE, AccountEntityColumns.BILLING_CC_LAST4, AccountEntityColumns.BILLING_STREET1, AccountEntityColumns.BILLING_STREET2, AccountEntityColumns.BILLING_CITY, AccountEntityColumns.BILLING_STATE, AccountEntityColumns.BILLING_ZIP, AccountEntityColumns.BILLING_ZIP_PLUS4, AccountEntityColumns.PLACE_IDS, AccountEntityColumns.SUBSCRIPTION_ID_MAP, AccountEntityColumns.OWNER, AccountEntityColumns.TRIAL_END }; private final PreparedStatement listPaged; private final PreparedStatement listAll; @Inject public AccountDAOImpl(Session session) { super(session, TABLE, COLUMN_ORDER); listPaged = CassandraQueryBuilder .select(TABLE) .addColumns(BASE_COLUMN_ORDER).addColumns(COLUMN_ORDER) .where("token(" + BaseEntityColumns.ID + ") >= token(?) LIMIT ?") .prepare(session); listAll = CassandraQueryBuilder .select(TABLE) .addColumns(BASE_COLUMN_ORDER).addColumns(COLUMN_ORDER) .prepare(session); } @Override protected List<Object> getValues(Account entity) { List<Object> values = new LinkedList<Object>(); values.add(entity.getState()); values.add(entity.isBillable()); values.add(entity.getTaxExempt()); values.add(entity.getBillingFirstName()); values.add(entity.getBillingLastName()); values.add(entity.getBillingCCType()); values.add(entity.getBillingCCLast4()); values.add(entity.getBillingStreet1()); values.add(entity.getBillingStreet2()); values.add(entity.getBillingCity()); values.add(entity.getBillingState()); values.add(entity.getBillingZip()); values.add(entity.getBillingZipPlusFour()); values.add(entity.getPlaceIDs()); // Add subscription ID's as Map<String, String> to DB Map<String, String> subIDs = null; if (entity.getSubscriptionIDs() != null && !entity.getSubscriptionIDs().isEmpty()) { subIDs = new HashMap<String, String>(); for (Map.Entry<ServiceLevel, String> item : entity.getSubscriptionIDs().entrySet()) { subIDs.put(item.getKey().name(), item.getValue()); } } values.add(subIDs); values.add(entity.getOwner()); values.add(entity.getTrialEnd()); return values; } @Override protected Account createEntity() { return new Account(); } @Override protected void populateEntity(Row row, Account entity) { entity.setBillable(row.getBool(AccountEntityColumns.BILLABLE)); entity.setState(row.getString(AccountEntityColumns.STATE)); entity.setTaxExempt(row.getBool(AccountEntityColumns.TAX_EXEMPT)); entity.setBillingFirstName(row.getString(AccountEntityColumns.BILLING_FIRST_NAME)); entity.setBillingLastName(row.getString(AccountEntityColumns.BILLING_LAST_NAME)); entity.setBillingCCType(row.getString(AccountEntityColumns.BILLING_CC_TYPE)); entity.setBillingCCLast4(row.getString(AccountEntityColumns.BILLING_CC_LAST4)); entity.setBillingStreet1(row.getString(AccountEntityColumns.BILLING_STREET1)); entity.setBillingStreet2(row.getString(AccountEntityColumns.BILLING_STREET2)); entity.setBillingCity(row.getString(AccountEntityColumns.BILLING_CITY)); entity.setBillingState(row.getString(AccountEntityColumns.BILLING_STATE)); entity.setBillingZip(row.getString(AccountEntityColumns.BILLING_ZIP)); entity.setBillingZipPlusFour(row.getString(AccountEntityColumns.BILLING_ZIP_PLUS4)); Set<UUID> placeIDs = row.getSet(AccountEntityColumns.PLACE_IDS, UUID.class); entity.setPlaceIDs(placeIDs == null || placeIDs.isEmpty() ? null : placeIDs); // Convert Subscription ID's to Map<ServiceLevel, String> if not empty Map<ServiceLevel, String> subIDs = null; Map<String, String> rowMap = row.getMap(AccountEntityColumns.SUBSCRIPTION_ID_MAP, String.class, String.class); if (rowMap != null && !rowMap.isEmpty()) { subIDs = new HashMap<ServiceLevel, String>(); for (Map.Entry<String, String> item : rowMap.entrySet()) { subIDs.put(ServiceLevel.valueOf(item.getKey()), item.getValue()); } } entity.setSubscriptionIDs(subIDs); entity.setOwner(row.getUUID(AccountEntityColumns.OWNER)); entity.setTrialEnd(row.getTimestamp(AccountEntityColumns.TRIAL_END)); } @Override public Account create(Account account) { UUID id = account.getId(); if(id == null) { id = nextId(account); } return doInsert(id, account); } @Override protected UUID getIdFromRow(Row row) { return row.getUUID(BaseEntityColumns.ID); } @Override protected UUID nextId(Account account) { return UUID.randomUUID(); } @Override public ModelEntity findAccountModelById(UUID id) { Account account = findById(id); return toModel(account); } private ModelEntity toModel(Account account) { if(account == null) { return null; } ModelEntity entity = new ModelEntity(toAttributes(account)); entity.setCreated(account.getCreated()); entity.setModified(account.getModified()); return entity; } private Map<String,Object> toAttributes(Account account) { Map<String,Object> attrs = new HashMap<>(); setIf(AccountCapability.ATTR_BILLINGCCLAST4, account.getBillingCCLast4(), attrs); setIf(AccountCapability.ATTR_BILLINGCCTYPE, account.getBillingCCType(), attrs); setIf(AccountCapability.ATTR_BILLINGCITY, account.getBillingCity(), attrs); setIf(AccountCapability.ATTR_BILLINGFIRSTNAME, account.getBillingFirstName(), attrs); setIf(AccountCapability.ATTR_BILLINGLASTNAME, account.getBillingLastName(), attrs); setIf(AccountCapability.ATTR_BILLINGSTATE, account.getBillingState(), attrs); setIf(AccountCapability.ATTR_BILLINGSTREET1, account.getBillingStreet1(), attrs); setIf(AccountCapability.ATTR_BILLINGSTREET2, account.getBillingStreet2(), attrs); setIf(AccountCapability.ATTR_BILLINGZIP, account.getBillingZip(), attrs); setIf(AccountCapability.ATTR_BILLINGZIPPLUSFOUR, account.getBillingZipPlusFour(), attrs); setIf(AccountCapability.ATTR_OWNER, account.getOwner(), attrs); setIf(AccountCapability.ATTR_STATE, account.getState(), attrs); setIf(AccountCapability.ATTR_TAXEXEMPT, account.getTaxExempt(), attrs); setIf(AccountCapability.ATTR_CREATED,account.getCreated(),attrs); setIf(AccountCapability.ATTR_MODIFIED,account.getModified(),attrs); setIf(Capability.ATTR_ADDRESS, account.getAddress(), attrs); setIf(Capability.ATTR_CAPS, account.getCaps(), attrs); setIf(Capability.ATTR_ID, account.getId(), attrs); setIf(Capability.ATTR_TAGS, account.getTags(), attrs); setIf(Capability.ATTR_TYPE, account.getType(), attrs); return attrs; } private void setIf(String key, Object val, Map<String,Object> attrs) { if(val != null) { if(val instanceof UUID) { val = val.toString(); } attrs.put(key, val); } } @Override public PagedResults<Account> listAccounts(AccountQuery query) { BoundStatement bs = null; if (query.getToken() != null) { bs = listPaged.bind(UUID.fromString(query.getToken()), query.getLimit() + 1); } else { bs = listPaged.bind(IrisUUID.nilUUID(), query.getLimit() + 1); } try(Context ctxt = listAccountsTimer.time()) { return doList(bs, query.getLimit()); } } @Override public Stream<Account> streamAll() { try(Context ctxt = streamAllTimer.time()) { Iterator<Row> rows = session.execute(new BoundStatement(listAll)).iterator(); Iterator<Account> result = Iterators.transform(rows, (row) -> buildEntity(row)); Spliterator<Account> stream = Spliterators.spliteratorUnknownSize(result, Spliterator.IMMUTABLE | Spliterator.NONNULL); return StreamSupport.stream(stream, false); } } }