text:
/**
 *
 */
package org.socraticgrid.hl7.services.uc.db;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.ibatis.session.SqlSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.socraticgrid.hl7.services.uc.db.dto.DTOMessageType;
import org.socraticgrid.hl7.services.uc.db.dto.UCSDto;
import org.socraticgrid.hl7.services.uc.exceptions.PersistenceException;
import org.socraticgrid.hl7.services.uc.exceptions.ProcessingException;
import org.socraticgrid.hl7.services.uc.model.AddressPersistenceAdapter;
import org.socraticgrid.hl7.services.uc.model.AlertMessage;
import org.socraticgrid.hl7.services.uc.model.AlertMessageHeader;
import org.socraticgrid.hl7.services.uc.model.AlertStatus;
import org.socraticgrid.hl7.services.uc.model.BaseAddress;
import org.socraticgrid.hl7.services.uc.model.Conversation;
import org.socraticgrid.hl7.services.uc.model.ConversationChannel;
import org.socraticgrid.hl7.services.uc.model.ConversationRequestMessage;
import org.socraticgrid.hl7.services.uc.model.ConversationRequestMessageHeader;
import org.socraticgrid.hl7.services.uc.model.DeliveryAddress;
import org.socraticgrid.hl7.services.uc.model.Message;
import org.socraticgrid.hl7.services.uc.model.MessageBody;
import org.socraticgrid.hl7.services.uc.model.MessageBodyExceptionsDelegate;
import org.socraticgrid.hl7.services.uc.model.MessageHeader;
import org.socraticgrid.hl7.services.uc.model.MessageHeaderPersistenceAdapter;
import org.socraticgrid.hl7.services.uc.model.MessageType;
import org.socraticgrid.hl7.services.uc.model.NotificationMessage;
import org.socraticgrid.hl7.services.uc.model.NotificationMessageHeader;
import org.socraticgrid.hl7.services.uc.model.PhysicalAddress;
import org.socraticgrid.hl7.services.uc.model.Recipient;
import org.socraticgrid.hl7.services.uc.model.SimpleMessage;
import org.socraticgrid.hl7.services.uc.model.SimpleMessageHeader;
import org.socraticgrid.hl7.services.uc.model.UserContactInfo;
import org.socraticgrid.hl7.services.uc.model.type.RecipientType;

/**
 * @author steven
 * @created Jan 16, 2014

 */
public class UCSDataService {

	private static final Logger log = LoggerFactory.getLogger(UCSDataService.class);

	/*********** ADDRESS
	 * |: Address
	 */
	public static void selectAddressById(UCSDto<AddressPersistenceAdapter> dto){
		AddressPersistenceAdapter address = null;
		String id = dto.getAdHocParams().get("addressId");
		SqlSession sqlSession = null;
		try{
			sqlSession = UCSDataConnection.getSqlSession();
			address = sqlSession.selectOne("selectAddressById", id);
			if(address == null){
				dto.getMsgs().put(DTOMessageType.WARN, "An Address for ID = "+id+" could not be found.");
			}
			else {
				dto.getListT().add(address);
			}
		}
		catch(Exception e){
			dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for Address ID = "+id+". Please check your parameters and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			log.error("******************* ERROR: ID = "+id+" errored with - "+e.toString(), e);
		}
		finally{
			if(sqlSession!=null){
				sqlSession.close();
			}
		}
	}
	public static void updateAddress(UCSDto<AddressPersistenceAdapter> dto){
		SqlSession sqlSession = null;
		try{
			sqlSession = UCSDataConnection.getSqlSession();
			for(AddressPersistenceAdapter address : dto.getListT()) {
				int updated = sqlSession.update("updateAddress", address);
				if(updated == 0){
					dto.getMsgs().put(DTOMessageType.WARN, "Address ID = "+address.getAddressId()+" could not be updated.");
				}
			}
			sqlSession.commit();
		}
		catch(Exception e){
			dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for while updating addresses. Please check your Address and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			log.error("******************* ERROR: Address update errored with - "+e.toString(), e);
		}
		finally{
			if(sqlSession!=null){
				sqlSession.close();
			}
		}
	}
	public static void insertAddress(UCSDto<AddressPersistenceAdapter> dto){
		SqlSession sqlSession = null;
		try{
			sqlSession = UCSDataConnection.getSqlSession();
			for(AddressPersistenceAdapter address : dto.getListT()) {
				int updated = sqlSession.insert("insertAddress", address);
				if(updated == 0){
					dto.getMsgs().put(DTOMessageType.WARN, "Address ID = "+address.getAddressId()+" could not be inserted.");
				}
			}
			sqlSession.commit();
		}
		catch(Exception e){
			dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for while inserting addresses. Please check your Address and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			log.error("******************* ERROR: Address update errored with - "+e.toString(), e);
		}
		finally{
			if(sqlSession!=null){
				sqlSession.close();
			}
		}
	}

	/*********** USER CONTACT INFO
	 * |: UserContactInfo
	 */
	public static void selectUserContactInfoById(UCSDto<UserContactInfo> dto){
		UserContactInfo userContactInfo = null;
		String id = dto.getAdHocParams().get("userContactInfoId");
		SqlSession sqlSession = null;
		try{
			sqlSession = UCSDataConnection.getSqlSession();
			userContactInfo = sqlSession.selectOne("selectUserContactInfoById", id);
			if(userContactInfo == null){
				dto.getMsgs().put(DTOMessageType.WARN, "A UserContactInfo for ID = "+id+" could not be found.");
			}
			else {
				dto.getListT().add(userContactInfo);
			}
		}
		catch(Exception e){
			dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for UserContactInfo ID = "+id+". Please check your parameters and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			log.error("******************* ERROR: UserContactInfo ID = "+id+" errored with - "+e.toString(), e);
		}
		finally{
			if(sqlSession!=null){
				sqlSession.close();
			}
		}
	}
	public static void updateUserContactInfo(UCSDto<UserContactInfo> dto){
		SqlSession sqlSession = null;
		try{
			sqlSession = UCSDataConnection.getSqlSession();

			upsertUserContactInfo(dto, sqlSession, true);

			//Check to see if an ERROR or WARN was generated
			if(	dto.getMsgs().containsKey(DTOMessageType.ERROR)
				|| dto.getMsgs().containsKey(DTOMessageType.WARN) ) {

				dto.getMsgs().putAll(dto.getMsgs());
				//Abort and let the close rollback
				return;
			}

			//If no errors commit
			sqlSession.commit();
		}
		catch(Exception e){
			dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for while updating UserContactInfo. Please check your UserContactInfo and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			log.error("******************* ERROR: UserContactInfo update errored with - "+e.toString(), e);
		}
		finally{
			if(sqlSession!=null){
				sqlSession.close();
			}
		}
	}
	public static void insertUserContactInfo(UCSDto<UserContactInfo> dto){
		SqlSession sqlSession = null;
		try{
			sqlSession = UCSDataConnection.getSqlSession();

			upsertUserContactInfo(dto, sqlSession, true);

			//Check to see if an ERROR or WARN was generated
			if(	dto.getMsgs().containsKey(DTOMessageType.ERROR)
				|| dto.getMsgs().containsKey(DTOMessageType.WARN) ) {

				dto.getMsgs().putAll(dto.getMsgs());
				//Abort and let the close rollback
				return;
			}

			//If no errors commit
			sqlSession.commit();
		}
		catch(Exception e){
			dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for while inserting UserContactInfo. Please check your UserContactInfo and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			log.error("******************* ERROR: UserContactInfo update errored with - "+e.toString(), e);
		}
		finally{
			if(sqlSession!=null){
				sqlSession.close();
			}
		}
	}
	/**
	 * Will update UserContactInfo and update/insert the preferred Address.
	 * If insert == true then if the UserContactInfo does not exist it will be inserted.
	 * NOTE: IMPORTANT: The SqlSession and Transaction will need to be  controlled by the caller
	 * @param dto
	 */
	public static void upsertUserContactInfo(UCSDto<UserContactInfo> dto, SqlSession sqlSession, boolean insert){
		try{
			for(UserContactInfo userContactInfo : dto.getListT()) {
				//Do the addresses first - they are all PhysicalAddress otherwise construct a new list.
				for(PhysicalAddress address : userContactInfo.getAddressesByType().values()) {
					AddressPersistenceAdapter adapter = new AddressPersistenceAdapter(address);
					int updated = sqlSession.update("updateAddress", adapter);
					if(updated == 0 ){
						updated = sqlSession.insert("insertAddress", adapter);
					}
					if(updated == 0){
						dto.getMsgs().put(DTOMessageType.WARN, "Address ID = "+adapter.getAddressId()+" could not be inserted for UserContactInfo ID "
								+userContactInfo.getUserContactInfoId());
						//Abort and let the close rollback
						return;
					}
				}
				//Do the PreferredAddress
				AddressPersistenceAdapter adapter = new AddressPersistenceAdapter(userContactInfo.getPreferredAddress());
				int updated = sqlSession.update("updateAddress", adapter);
				if(updated == 0 ){
					updated = sqlSession.insert("insertAddress", adapter);
				}
				if(updated == 0){
					dto.getMsgs().put(DTOMessageType.WARN, "Address ID = "+adapter.getAddressId()+" could not be inserted for UserContactInfo ID "
							+userContactInfo.getUserContactInfoId());
					//Abort and let the close rollback
					return;
				}

				updated = sqlSession.update("updateUserContactInfo", userContactInfo);
				if(insert && updated == 0 ){
					updated = sqlSession.insert("insertUserContactInfo", userContactInfo);
				}
				if(updated == 0){
					dto.getMsgs().put(DTOMessageType.WARN, "UserContactInfo ID = "+userContactInfo.getUserContactInfoId()+" could not be updated.");
					//Abort and let the close rollback
					return;
				}

				//If all succeeded rebuild the user_contact_info_join_address table
				updated = sqlSession.delete("deleteForUserContactInfoId",userContactInfo.getUserContactInfoId());

				//Build the Recipient join table
				Map<String,String> params = new HashMap<>();
				Set<String> types = userContactInfo.getAddressesByType().keySet();
				for(String type : types){
					BaseAddress address = userContactInfo.getAddressesByType().get(type);
					params.clear();
					params.put("userContactInfoId",userContactInfo.getUserContactInfoId());
					params.put("addressId",address.getAddressId());
					params.put("type",type);
					updated = sqlSession.insert("insertUserContactInfoJoinAddress", params);
					if(updated == 0){
						dto.getMsgs().put(DTOMessageType.WARN, "UserContactInfo Join Address table could not be created for UserContactInfo Id = '"
								+userContactInfo.getUserContactInfoId()+"'");
						//Abort and let the close rollback
						return;
					}
				}

			}
		}
		catch(Exception e){
			dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for while updating addresses. Please check your Address and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			log.error("******************* ERROR: Address update errored with - "+e.toString(), e);
		}
	}

	/*********** CONVERSATION
	 * |: Conversation
	 */
	public static void selectConversationId(UCSDto<Conversation> dto){
		Conversation conversation = null;
		String id = dto.getAdHocParams().get("conversationId");
		SqlSession sqlSession = null;
		try{
			sqlSession = UCSDataConnection.getSqlSession();
			conversation = sqlSession.selectOne("selectConversationById", id);
			if(conversation == null){
				dto.getMsgs().put(DTOMessageType.WARN, "A Conversation for ID = "+id+" could not be found.");
			}
			else {
				dto.getListT().add(conversation);
			}
		}
		catch(Exception e){
			dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for Conversation ID = "+id+". Please check your parameters and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			log.error("******************* ERROR: ID = "+id+" errored with - "+e.toString(), e);
		}
		finally{
			if(sqlSession!=null){
				sqlSession.close();
			}
		}
	}
	public static void updateConversation(UCSDto<Conversation> dto){
		SqlSession sqlSession = null;
		try{
			sqlSession = UCSDataConnection.getSqlSession();

			upsertConversation(dto, sqlSession, true);

			//Check to see if an ERROR or WARN was generated
			if(	dto.getMsgs().containsKey(DTOMessageType.ERROR)
				|| dto.getMsgs().containsKey(DTOMessageType.WARN) ) {

				dto.getMsgs().putAll(dto.getMsgs());
				//Abort and let the close rollback
				return;
			}

			//If no errors commit
			sqlSession.commit();
		}
		catch(Exception e){
			dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for while updating Conversation. Please check your Conversation and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			log.error("******************* ERROR: Conversation update errored with - "+e.toString(), e);
		}
		finally{
			if(sqlSession!=null){
				sqlSession.close();
			}
		}
	}
	public static void insertConversation(UCSDto<Conversation> dto){
		SqlSession sqlSession = null;
		try{
			sqlSession = UCSDataConnection.getSqlSession();

			upsertConversation(dto, sqlSession, true);

			//Check to see if an ERROR or WARN was generated
			if(	dto.getMsgs().containsKey(DTOMessageType.ERROR)
				|| dto.getMsgs().containsKey(DTOMessageType.WARN) ) {

				dto.getMsgs().putAll(dto.getMsgs());
				//Abort and let the close rollback
				return;
			}

			//If no errors commit
			sqlSession.commit();
		}
		catch(Exception e){
			dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for while inserting Conversation. Please check your Conversation and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			log.error("******************* ERROR: Conversation update errored with - "+e.toString(), e);
		}
		finally{
			if(sqlSession!=null){
				sqlSession.close();
			}
		}
	}
	/**
	 * Will update Conversation and update/insert the Owner (UserContactInfo), ConversationChannels, Participants (Address).
	 * If insert == true then if the Conversation does not exist it will be inserted.
	 * NOTE: IMPORTANT: The SqlSession and Transaction will need to be  controlled by the caller
	 * @param dto
	 */
	public static void upsertConversation(UCSDto<Conversation> dto, SqlSession sqlSession, boolean insert){
		try{
			for(Conversation conversation : dto.getListT()) {
				//Do the Owner first
				if(conversation.getOwner()!=null){
					UCSDto<UserContactInfo> ownerDto = new UCSDto<>();
					ownerDto.getListT().add(conversation.getOwner());
					upsertUserContactInfo(ownerDto,sqlSession,true);
					//Check to see if an ERROR or WARN was generated
					if(	ownerDto.getMsgs().containsKey(DTOMessageType.ERROR)
						|| ownerDto.getMsgs().containsKey(DTOMessageType.WARN) ) {

						dto.getMsgs().putAll(ownerDto.getMsgs());
						//Abort and let the close rollback
						return;
					}
					else{
						//Get any INFO messages that might be generated
						dto.getMsgs().putAll(ownerDto.getMsgs());
					}
				}

				//Do the channels
				for(ConversationChannel channel : conversation.getChannels()) {
					int updated = sqlSession.update("updateConversationChannel", channel);
					if(updated == 0 ){
						updated = sqlSession.insert("insertConversationChannel", channel);
					}
					if(updated == 0){
						dto.getMsgs().put(DTOMessageType.WARN, "CoversationChannel ID = "+channel.getConversationChannelId()+" could not be inserted for Conversation ID "
								+conversation.getConversationId());
						//Abort and let the close rollback
						return;
					}
				}

				//Do the Participants
				UCSDto<DeliveryAddress> participantDto = new UCSDto<>();
				participantDto.getListT().addAll(conversation.getParticipants());
				upsertDeliveryAddress(participantDto, sqlSession, true);
				//Check to see if an ERROR or WARN was generated
				if(	participantDto.getMsgs().containsKey(DTOMessageType.ERROR)
					|| participantDto.getMsgs().containsKey(DTOMessageType.WARN) ) {

					dto.getMsgs().putAll(participantDto.getMsgs());
					//Abort and let the close rollback
					return;
				}
				else{
					//Get any INFO messages that might be generated
					dto.getMsgs().putAll(participantDto.getMsgs());
				}

				//Do the Conversation
				int updated = sqlSession.update("updateConversation", conversation);
				if(updated == 0 ){
					updated = sqlSession.insert("insertConversation", conversation);
				}
				if(updated == 0){
					dto.getMsgs().put(DTOMessageType.WARN, "Coversation ID = "+conversation.getConversationId()+" could not be inserted or updated. ");
					//Abort and let the close rollback
					return;
				}

				//Delete existing Conversation ConversationChannel joins
				sqlSession.delete("deleteForConversationIdConversationChannelJoin",conversation.getConversationId());

				//Delete existing Conversation Participant joins
				sqlSession.delete("deleteForConversationIdParticipantJoin",conversation.getConversationId());

				//Build the Conversation ConversationChannel join table
				Map<String,String> params = new HashMap<>();
				for(ConversationChannel channel : conversation.getChannels()){
					params.clear();
					params.put("conversationId",conversation.getConversationId());
					params.put("conversationChannelId",channel.getConversationChannelId());
					updated = sqlSession.insert("insertConversationJoinConversationChannel", params);
					if(updated == 0){
						dto.getMsgs().put(DTOMessageType.WARN, "Conversation-ConversationChannel Join table could not be created for Conversation Id = '"
								+ conversation.getConversationId()+"' for ConversationChannel Id = '"+channel.getConversationChannelId()+"'.");
						//Abort and let the close rollback
						return;
					}
				}

				//Build the Conversation Participants join table
				params = new HashMap<>();
				for(DeliveryAddress participant : conversation.getParticipants()){
					params.clear();
					params.put("conversationId",conversation.getConversationId());
					params.put("deliveryAddressId",participant.getDeliveryAddressId());
					updated = sqlSession.insert("insertConversationJoinParticipant", params);
					if(updated == 0){
						dto.getMsgs().put(DTOMessageType.WARN, "Conversation-Participant Join table could not be created for Conversation Id = '"
								+ conversation.getConversationId()+"' for ConversationChannel Id = '"+participant.getDeliveryAddressId()+"'.");
						//Abort and let the close rollback
						return;
					}
				}

			}
		}
		catch(Exception e){
			dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for while updating Conversation. Please check your Conversation and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			log.error("******************* ERROR: Conversation update errored with - "+e.toString(), e);
		}
	}

	/*********** DELIVERY ADDRESS
	 * |: DeliveryAddress
	 */
	public static void selectDeliveryAddressById(UCSDto<DeliveryAddress> dto){
		DeliveryAddress address = null;
		String id = dto.getAdHocParams().get("deliveryAddressId");
		SqlSession sqlSession = null;
		try{
			sqlSession = UCSDataConnection.getSqlSession();
			address = sqlSession.selectOne("selectDeliveryAddressById", id);
			if(address == null){
				dto.getMsgs().put(DTOMessageType.WARN, "An DeliveryAddress for ID = "+id+" could not be found.");
			}
			else {
				dto.getListT().add(address);
			}
		}
		catch(Exception e){
			dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for DeliveryAddress ID = "+id+". Please check your parameters and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			log.error("******************* ERROR: DeliveryAddress ID = "+id+" errored with - "+e.toString(), e);
		}
		finally{
			if(sqlSession!=null){
				sqlSession.close();
			}
		}
	}
	/**
	 * Will update the DeliveryAddress and update/insert Address
	 * in a single Transaction - rollback the entire DeliveryAddress/Address list if fail.
	 * @param dto
	 */
	public static void updateDeliveryAddress(UCSDto<DeliveryAddress> dto){
		SqlSession sqlSession = null;
		try{
			sqlSession = UCSDataConnection.getSqlSession();

			upsertDeliveryAddress(dto, sqlSession, true);

			//Check to see if an ERROR or WARN was generated
			if(	dto.getMsgs().containsKey(DTOMessageType.ERROR)
				|| dto.getMsgs().containsKey(DTOMessageType.WARN) ) {

				dto.getMsgs().putAll(dto.getMsgs());
				//Abort and let the close rollback
				return;
			}
			else{
				//Get any INFO messages that might be generated
				dto.getMsgs().putAll(dto.getMsgs());
			}

			//If no errors commit
			sqlSession.commit();
		}
		catch(Exception e){
			dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for while updating DeliveryAddresses. Please check your DeliveryAddress and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			log.error("******************* ERROR: DeliveryAddress update errored with - "+e.toString(), e);
		}
		finally{
			if(sqlSession!=null){
				sqlSession.close();
			}
		}
	}
	/**
	 * Will update the DeliveryAddress and Address
	 * in a single Transaction - rollback the entire DeliveryAddress/Address list if fail.
	 * @param dto
	 */
	public static void insertDeliveryAddress(UCSDto<DeliveryAddress> dto){
		SqlSession sqlSession = null;
		try{
			sqlSession = UCSDataConnection.getSqlSession();

			upsertDeliveryAddress(dto, sqlSession, true);

			//Check to see if an ERROR or WARN was generated
			if(	dto.getMsgs().containsKey(DTOMessageType.ERROR)
				|| dto.getMsgs().containsKey(DTOMessageType.WARN) ) {

				dto.getMsgs().putAll(dto.getMsgs());
				//Abort and let the close rollback
				return;
			}
			else{
				//Get any INFO messages that might be generated
				dto.getMsgs().putAll(dto.getMsgs());
			}

			//If no errors commit
			sqlSession.commit();
		}
		catch(Exception e){
			dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for while inserting DeliveryAddress. Please check your DeliveryAddress and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			log.error("******************* ERROR: DeliveryAddress update errored with - "+e.toString(), e);
		}
		finally{
			if(sqlSession!=null){
				sqlSession.close();
			}
		}
	}
	/**
	 * Will update DeliveryAddress and update/insert the Address.
	 * If insert == true then if the DeliveryAddress does not exist it will be inserted.
	 * NOTE: IMPORTANT: The SqlSession and Transaction will need to be  controlled by the caller
	 * @param dto
	 */
	public static void upsertDeliveryAddress(UCSDto<DeliveryAddress> dto, SqlSession sqlSession, boolean insert){
		try{
			for(DeliveryAddress address : dto.getListT()) {
				//Do the address first
				AddressPersistenceAdapter adapter = new AddressPersistenceAdapter(address.getAddress());
				int updated = sqlSession.update("updateAddress", adapter);
				if(updated == 0 ){
					updated = sqlSession.insert("insertAddress", adapter);
				}
				if(updated == 0){
					//TODO: Log this somewhere
					dto.getMsgs().put(DTOMessageType.WARN, "Address ID = "+adapter.getAddressId()+" could not be inserted for DeliveryAddress ID "+address.getDeliveryAddressId());
					//Abort and let the close rollback
					return;
				}

				updated = sqlSession.update("updateDeliveryAddress", address);
				if(insert && updated == 0 ){
					updated = sqlSession.insert("insertDeliveryAddress", address);
				}
				if(updated == 0){
					dto.getMsgs().put(DTOMessageType.WARN, "DeliveryAddress ID = "+address.getDeliveryAddressId()+" could not be updated.");
					//Abort and let the close rollback
					return;
				}
			}
		}
		catch(Exception e){
			dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for while updating DeliveryAddresses. Please check your DeliveryAddress and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			log.error("******************* ERROR: DeliveryAddress update errored with - "+e.toString(), e);
		}
	}

	/*********** MESSAGE BODY
	 * |: Message Body
	 */
	public static void selectMessageBodyById(UCSDto<MessageBody> dto){
		MessageBody messageBody = null;
		String id = dto.getAdHocParams().get("messageBodyId");
		SqlSession sqlSession = null;
		try{
			sqlSession = UCSDataConnection.getSqlSession();
			messageBody = sqlSession.selectOne("selectMessageBodyById", id);
			if(messageBody == null){
				dto.getMsgs().put(DTOMessageType.WARN, "A MessageBody for ID = "+id+" could not be found.");
			}
			else {
				dto.getListT().add(messageBody);
			}
		}
		catch(Exception e){
			dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for MessageBody ID = "+id+". Please check your parameters and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			log.error("******************* ERROR: MessageBody ID = "+id+" errored with - "+e.toString(), e);
		}
		finally{
			if(sqlSession!=null){
				sqlSession.close();
			}
		}
	}
	public static void updateMessageBody(UCSDto<MessageBody> dto){
		SqlSession sqlSession = null;
		try{
			sqlSession = UCSDataConnection.getSqlSession();

			upsertMessageBody(dto, sqlSession, true);

			//Check to see if an ERROR or WARN was generated
			if(	dto.getMsgs().containsKey(DTOMessageType.ERROR)
				|| dto.getMsgs().containsKey(DTOMessageType.WARN) ) {

				dto.getMsgs().putAll(dto.getMsgs());
				//Abort and let the close rollback
				return;
			}
			else{
				//Get any INFO messages that might be generated
				dto.getMsgs().putAll(dto.getMsgs());
			}
			sqlSession.commit();
		}
		catch(Exception e){
			dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for while updating MessageBody. Please check your MessageBody and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			log.error("******************* ERROR: MessageBody update errored with - "+e.toString(), e);
		}
		finally{
			if(sqlSession!=null){
				sqlSession.close();
			}
		}
	}
	public static void insertMessageBody(UCSDto<MessageBody> dto){
		SqlSession sqlSession = null;
		try{
			sqlSession = UCSDataConnection.getSqlSession();

			upsertMessageBody(dto, sqlSession, true);

			//Check to see if an ERROR or WARN was generated
			if(	dto.getMsgs().containsKey(DTOMessageType.ERROR)
				|| dto.getMsgs().containsKey(DTOMessageType.WARN) ) {

				dto.getMsgs().putAll(dto.getMsgs());
				//Abort and let the close rollback
				return;
			}
			else{
				//Get any INFO messages that might be generated
				dto.getMsgs().putAll(dto.getMsgs());
			}

			sqlSession.commit();
		}
		catch(Exception e){
			dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for while inserting MessageBody. Please check your MessageBody and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			log.error("******************* ERROR: MessageBody update errored with - "+e.toString(), e);
		}
		finally{
			if(sqlSession!=null){
				sqlSession.close();
			}
		}
	}
		/**
		 * Will update MessageBody
		 * If insert == true then if the MessageBody does not exist it will be inserted.
		 * NOTE: IMPORTANT: The SqlSession and Transaction will need to be  controlled by the caller
		 * @param dto
		 */
	public static void upsertMessageBody(UCSDto<MessageBody> dto, SqlSession sqlSession, boolean insert){
		try{
			for(MessageBody messageBody : dto.getListT()) {
				int updated = sqlSession.insert("updateMessageBody", messageBody);
				if(insert && updated == 0 ){
					updated = sqlSession.insert("insertMessageBody", messageBody);
				}
				if(updated == 0){
					dto.getMsgs().put(DTOMessageType.WARN, "MessageBody ID = "+messageBody.getMessageBodyId()+" could not be inserted.");
					//Abort and let the close rollback
					return;
				}
			}
		}
		catch(Exception e){
			dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for while inserting MessageBody. Please check your MessageBody and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			log.error("******************* ERROR: MessageBody update errored with - "+e.toString(), e);
		}
	}

	/*********** MESSAGE HEADER
	 * |: Message Header
	 */
	public static <T extends MessageHeader> void selectMessageHeaderById(UCSDto<T> dto){
		selectMessageHeader(dto, MessageHeaderSelectType.BYID);
	}
	@SuppressWarnings("unchecked")
	public static <T extends MessageHeader> void selectMessageHeader(UCSDto<T> dto, MessageHeaderSelectType type){
		MessageHeaderPersistenceAdapter messageAdapter = null;
		String id = dto.getAdHocParams().get(type.getQueryId());

		SqlSession sqlSession = null;
		try{
			sqlSession = UCSDataConnection.getSqlSession();
			messageAdapter = sqlSession.selectOne(type.getQueryId(), id);
			if(messageAdapter == null){
				if(type.equals(MessageHeaderSelectType.BYID)){
					dto.getMsgs().put(DTOMessageType.WARN, "A MessageHeader for ID = "+id+" could not be found.");
				}
				else {
					dto.getMsgs().put(DTOMessageType.WARN, "A Message for ID = "+id+" could not be found.");
				}
			}
			else {
				dto.getListT().add((T)messageAdapter.buildMessageHeader());
			}
		}
		catch(Exception e){
			if(type.equals(MessageHeaderSelectType.BYID)){
				dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for MessageHeader ID = "+id+". Please check your parameters and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			}
			else{
				dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for Message ID = "+id+". Please check your parameters and resubmit."+
						" If you continue to receive errors please advise your system administrator.");
				}

			log.error("******************* ERROR: ID = "+id+" errored with - "+e.toString(), e);
		}
		finally{
			if(sqlSession!=null){
				sqlSession.close();
			}
		}
	}
	/**
	 * Will update MessageHeader and update/insert Recipient, DeliveryAddress and Address
	 * in a single Transaction - rollback all MessageHeaders if fail.
	 * This has the potential of locking more than a couple of tables.
	 * @param dto
	 */
	public static void updateMessageHeader(UCSDto<? extends MessageHeader> dto){
		SqlSession sqlSession = null;
		try{
			sqlSession = UCSDataConnection.getSqlSession();

			upsertMessageHeader(dto, sqlSession, true);

			//Check to see if we should commit
			if(	! (dto.getMsgs().containsKey(DTOMessageType.ERROR)
				|| dto.getMsgs().containsKey(DTOMessageType.WARN) )) {

				sqlSession.commit();
			}
		}
		catch(Exception e){
			dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for while inserting MessageHeader. Please check your MessageHeader and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			log.error("******************* ERROR: MessageHeader update errored with - "+e.toString(), e);
		}
		finally{
			if(sqlSession!=null){
				sqlSession.close();
			}
		}
	}
	public static void insertMessageHeader(UCSDto<? extends MessageHeader> dto){
		SqlSession sqlSession = null;
		try{
			sqlSession = UCSDataConnection.getSqlSession();
			upsertMessageHeader(dto, sqlSession, true);

			//Check to see if we should commit
			if(	! (dto.getMsgs().containsKey(DTOMessageType.ERROR)
				|| dto.getMsgs().containsKey(DTOMessageType.WARN) )) {

				sqlSession.commit();
			}
		}
		catch(Exception e){
			dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for while inserting MessageHeader. Please check your MessageHeader and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			log.error("******************* ERROR: MessageHeader update errored with - "+e.toString(), e);
		}
		finally{
			if(sqlSession!=null){
				sqlSession.close();
			}
		}
	}
	/**
	 * This method is used by MessageHeader insert,update
	 * as well as Message insert,update
	 */
	static void upsertMessageHeader(UCSDto<? extends MessageHeader> dto, SqlSession sqlSession, boolean deleteJoins){

		for(MessageHeader messageHeader : dto.getListT()) {
			MessageHeaderPersistenceAdapter messageAdapter = new MessageHeaderPersistenceAdapter(messageHeader);
			int updated = 0;

			/**
			 * NotificationMessageHeader set a list of recipients that are
			 * VisibleReceivers.  This list is used to make the join in
			 * MessageHeaderJoinRecipient with a RecipientType = RecipientType:VISIBLE_RECEIVER
			 */
			List<String> visibleReceiverIds = new ArrayList<>();

			/**
			 * AlertMessageHeader set a list of DeliveryAddress that are
			 * matched to an AlertStatus.  This list is used to make the join in
			 * MessageHeaderAlertStatusJoinDeliveryAddress
			 */
			Map<DeliveryAddress, AlertStatus> statusByReceiver = new LinkedHashMap<>();

			/**
			 * Recipient and MessageHeader have a many-to-one relationship which is kept in the
			 * message_header_join_recipient table.  The reason for the join table vis-a-vis a
			 * message_header_id column in recipient is that it is easier to manage. For instance to "update"
			 * recipients:
			 * delete the association from the join table.
			 * update the recipients and or insert new recipients and their data
			 * remake the join table using the ids in the list
			 *
			 * In this way the association doesn't touch the actual recipient (e.g. using deleted)
			 * otherwise it becomes problematic to find which recipients have been dropped from the
			 * previous association and update their deleted to 1.
			 */
			UCSDto<Recipient> recipientDto = new UCSDto<>();
			List<Recipient> recipientList = new ArrayList<>();
			recipientList.addAll(messageAdapter.getRecipientsList());
			recipientDto.setListT(recipientList);
			upsertRecipient(recipientDto, sqlSession, true);

			//Check to see if an ERROR or WARN was generated
			if(	recipientDto.getMsgs().containsKey(DTOMessageType.ERROR)
				|| recipientDto.getMsgs().containsKey(DTOMessageType.WARN) ) {

				dto.getMsgs().putAll(recipientDto.getMsgs());
				//Abort and let the close rollback
				return;
			}
			else{
				//Get any INFO messages that might be generated
				dto.getMsgs().putAll(recipientDto.getMsgs());
			}

			/**
			 * Do the Sender
			 */
			UCSDto<DeliveryAddress> senderDto = new UCSDto<>();
			senderDto.getListT().add(messageAdapter.getSender());
			upsertDeliveryAddress(senderDto, sqlSession, true);

			//Check to see if an ERROR or WARN was generated
			if(	senderDto.getMsgs().containsKey(DTOMessageType.ERROR)
				|| senderDto.getMsgs().containsKey(DTOMessageType.WARN) ) {

				dto.getMsgs().putAll(senderDto.getMsgs());
				//Abort and let the close rollback
				return;
			}
			else{
				//Get any INFO messages that might be generated
				dto.getMsgs().putAll(senderDto.getMsgs());
			}

			/*****************************
			 * Do Implementation specific persistence
			 */
			if( messageHeader instanceof SimpleMessageHeader )
			{
				//Do UserContactInfo
				UserContactInfo userContactInfo = ((SimpleMessageHeader)messageHeader).getReplyTo();
				UCSDto<UserContactInfo> userContactDto = new UCSDto<>();
				userContactDto.getListT().add(userContactInfo);
				upsertUserContactInfo(userContactDto, sqlSession, true);
				//Check to see if an ERROR or WARN was generated
				if(	userContactDto.getMsgs().containsKey(DTOMessageType.ERROR)
					|| userContactDto.getMsgs().containsKey(DTOMessageType.WARN) ) {

					dto.getMsgs().putAll(userContactDto.getMsgs());
					//Abort and let the close rollback
					return;
				}else{
					//Get any INFO messages that might be generated
					dto.getMsgs().putAll(recipientDto.getMsgs());
				}
			}
			if( messageHeader instanceof NotificationMessageHeader )
			{
				//Do UserContactInfo
				UserContactInfo userContactInfo = ((NotificationMessageHeader)messageHeader).getReplyTo();
				UCSDto<UserContactInfo> userContactDto = new UCSDto<>();
				userContactDto.getListT().add(userContactInfo);
				upsertUserContactInfo(userContactDto, sqlSession, true);
				//Check to see if an ERROR or WARN was generated
				if(	userContactDto.getMsgs().containsKey(DTOMessageType.ERROR)
					|| userContactDto.getMsgs().containsKey(DTOMessageType.WARN) ) {

					dto.getMsgs().putAll(userContactDto.getMsgs());
					//Abort and let the close rollback
					return;
				}else{
					//Get any INFO messages that might be generated
					dto.getMsgs().putAll(recipientDto.getMsgs());
				}

				//Do VisibleReceivers
				List<Recipient> visibleReceivers = new ArrayList<>();
				visibleReceivers.addAll(((NotificationMessageHeader)messageHeader).getVisableReceivers());
				UCSDto<Recipient> dtoRecipient = new UCSDto<>();
				dtoRecipient.getListT().addAll(visibleReceivers);
				upsertRecipient(dtoRecipient, sqlSession, true);
				//Check to see if an ERROR or WARN was generated
				if(	dtoRecipient.getMsgs().containsKey(DTOMessageType.ERROR)
					|| dtoRecipient.getMsgs().containsKey(DTOMessageType.WARN) ) {

					dto.getMsgs().putAll(dtoRecipient.getMsgs());
					//Abort and let the close rollback
					return;
				}else{
					//Get any INFO messages that might be generated
					dto.getMsgs().putAll(dtoRecipient.getMsgs());
					//Build the Visible Receivers ID list
					for(Recipient visibleReceiver : visibleReceivers){
						visibleReceiverIds.add(visibleReceiver.getRecipientId());
					}
				}
			}
			if( messageHeader instanceof ConversationRequestMessageHeader )
			{
				Conversation conversation = ((ConversationRequestMessageHeader)messageHeader).getConversation();
				UCSDto<Conversation> conversationDto = new UCSDto<>();
				conversationDto.getListT().add(conversation);
				upsertConversation(conversationDto, sqlSession, true);
				//Check to see if an ERROR or WARN was generated
				if(	conversationDto.getMsgs().containsKey(DTOMessageType.ERROR)
					|| conversationDto.getMsgs().containsKey(DTOMessageType.WARN) ) {

					dto.getMsgs().putAll(conversationDto.getMsgs());
					//Abort and let the close rollback
					return;
				}else{
					//Get any INFO messages that might be generated
					dto.getMsgs().putAll(conversationDto.getMsgs());
				}
			}
			if( messageHeader instanceof AlertMessageHeader )
			{
				statusByReceiver = ((AlertMessageHeader)messageHeader).getStatusByReciever();
				UCSDto<DeliveryAddress> daDto;
				for(DeliveryAddress da : statusByReceiver.keySet()){
					daDto = new UCSDto<>();
					daDto.getListT().add(da);
					upsertDeliveryAddress(daDto, sqlSession, true);
					//Check to see if an ERROR or WARN was generated
					if(	daDto.getMsgs().containsKey(DTOMessageType.ERROR)
						|| daDto.getMsgs().containsKey(DTOMessageType.WARN) ) {

						dto.getMsgs().putAll(daDto.getMsgs());
						//Abort and let the close rollback
						return;
					}else{
						//Get any INFO messages that might be generated
						dto.getMsgs().putAll(daDto.getMsgs());
					}
				}
			}

			/*****************************
			 * EndOf Implementation specific persistence
			 */

			//Do the MessageHeader
			updated = sqlSession.insert("updateMessageHeader", messageAdapter);
			if(updated == 0){
				//Try inserting
				updated = sqlSession.insert("insertMessageHeader", messageAdapter);
			}
			if(updated == 0){
				dto.getMsgs().put(DTOMessageType.WARN, "MessageHeader ID = "+messageAdapter.getMessageHeaderId()+" could not be inserted.");
				//Abort and let the close rollback
				return;
			}

			//IMPORTANT: Delete existing joins for ALL Types in message_header_join_recipient
			if(deleteJoins) {
				//This will delete both Recipients and
				updated = sqlSession.delete("deleteMessageHeaderJoinRecipientAndReceiverByMessageHeaderId",messageAdapter.getMessageHeaderId());
				updated = sqlSession.delete("deleteMessageHeaderAlertStatusByMessageHeaderId",messageAdapter.getMessageHeaderId());
				updated = sqlSession.delete("deleteMessageHeaderJoinSenderByMessageHeaderId",messageAdapter.getMessageHeaderId());
			}

			//Build the Recipient join table
			Set<Recipient> recipients = messageAdapter.getRecipientsList();
			Map<String,String> params = new HashMap<>();
			for(Recipient recipient : recipients){
				params.clear();
				params.put("messageHeaderId",messageAdapter.getMessageHeaderId());
				params.put("recipientId",recipient.getRecipientId());
				params.put("recipientType",RecipientType.RECIPIENT.name());
				updated = sqlSession.insert("insertMessageHeaderJoinRecipient", params);
				if(updated == 0){
					dto.getMsgs().put(DTOMessageType.WARN, "MessageHeader-Recipient Join table could not be created for MessageHeader Id = '"
							+messageAdapter.getMessageHeaderId()+"'.");
					//Abort and let the close rollback
					return;
				}
			}

			//If they exist build the Visible Receivers into Recipient join table
			params = new HashMap<>();
			for(String visibleReceiverId : visibleReceiverIds){
				params.clear();
				params.put("messageHeaderId",messageAdapter.getMessageHeaderId());
				params.put("recipientId",visibleReceiverId);
				params.put("recipientType",RecipientType.VISIBLE_RECEIVER.name());
				updated = sqlSession.insert("insertMessageHeaderJoinRecipient", params);
				if(updated == 0){
					dto.getMsgs().put(DTOMessageType.WARN, "MessageHeader-Recipient Join table could not be created for VisibleReceiver Id '"
							+visibleReceiverId+"' for MessageHeader Id = '"+messageAdapter.getMessageHeaderId()+"'.");
					//Abort and let the close rollback
					return;
				}
			}

			//If they exist build the AlertStatus into Receivers join table
			params = new HashMap<>();
			for(DeliveryAddress da : statusByReceiver.keySet()){
				params.clear();
				params.put("messageHeaderId",messageAdapter.getMessageHeaderId());
				params.put("deliveryAddressId",da.getDeliveryAddressId());
				params.put("alertStatus",statusByReceiver.get(da).name());
				updated = sqlSession.insert("insertMessageHeaderAlertStatusJoin", params);
				if(updated == 0){
					dto.getMsgs().put(DTOMessageType.WARN, "MessageHeader-AlertStatus Join table could not be created for DeliveryAddress Id '"
							+da.getDeliveryAddressId()+"' for MessageHeader Id = '"+messageAdapter.getMessageHeaderId()+"'.");
					//Abort and let the close rollback
					return;
				}
			}

			//Join the sender
			params = new HashMap<>();
			params.put("messageHeaderId",messageAdapter.getMessageHeaderId());
			params.put("deliveryAddressId",messageAdapter.getSender().getDeliveryAddressId());
			updated = sqlSession.insert("insertMessageHeaderJoinSender", params);
			if(updated == 0){
				dto.getMsgs().put(DTOMessageType.WARN, "MessageHeader-Sender Join table could not be created for DeliveryAddress Id '"
						+messageAdapter.getSender().getDeliveryAddressId()+"' for MessageHeader Id = '"+messageAdapter.getMessageHeaderId()+"'.");
				//Abort and let the close rollback
				return;
			}

		}

		//Finally - all succeeds - commit
		sqlSession.commit();

	}

	/*********** RECIPIENT
	 * |: Recipient
	 */
	public static void selectRecipientById(UCSDto<Recipient> dto){
		Recipient recipient = null;
		String id = dto.getAdHocParams().get("recipientId");
		SqlSession sqlSession = null;
		try{
			sqlSession = UCSDataConnection.getSqlSession();
			recipient = sqlSession.selectOne("selectRecipientById", id);
			if(recipient == null){
				dto.getMsgs().put(DTOMessageType.WARN, "A Recipient for ID = "+id+" could not be found.");
			}
			else {
				dto.getListT().add(recipient);
			}
		}
		catch(Exception e){
			dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for Recipient ID = "+id+". Please check your parameters and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			log.error("******************* ERROR: Recipient ID = "+id+" errored with - "+e.toString(), e);
		}
		finally{
			if(sqlSession!=null){
				sqlSession.close();
			}
		}
	}
	public static void updateRecipient(UCSDto<Recipient> dto){
		SqlSession sqlSession = null;
		try {
			sqlSession = UCSDataConnection.getSqlSession();
			upsertRecipient(dto, sqlSession, false);

			//Check to see if we should commit
			if(	! (dto.getMsgs().containsKey(DTOMessageType.ERROR)
				|| dto.getMsgs().containsKey(DTOMessageType.WARN) )) {

				sqlSession.commit();
			}
		}
		catch(Exception e){
			dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for while updating Recipient. Please check your Recipient and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			log.error("******************* ERROR: Recipient update errored with - "+e.toString(), e);
		}
		finally{
			if(sqlSession!=null){
				sqlSession.close();
			}
		}

	}
	/**
	 * Will update Recipient and update/insert DeliveryAddress and Address.
	 * If insert == true then if the Recipient does not exist it will be inserted.
	 * NOTE: IMPORTANT: The SqlSession and Transaction will need to be  controlled by the caller
	 * @param dto
	 */
	public static void upsertRecipient(UCSDto<Recipient> dto, SqlSession sqlSession, boolean insert){
		for(Recipient recipient : dto.getListT()) {
			//Do the address first
			AddressPersistenceAdapter adapter = new AddressPersistenceAdapter(recipient.getDeliveryAddress().getAddress());
			int updated = sqlSession.insert("updateAddress", adapter);
			if(updated == 0 ){
				updated = sqlSession.insert("insertAddress", adapter);
			}
			if(updated == 0){
				//TODO: Log this somewhere
				dto.getMsgs().put(DTOMessageType.WARN, "Address ID = "+adapter.getAddressId()+" could not be inserted for Recipient ID "+recipient.getRecipientId());
				//Abort and let the close rollback
				return;
			}

			//Do the DeliveryAddress
			DeliveryAddress deliveryAddress = recipient.getDeliveryAddress();
			updated = sqlSession.update("updateDeliveryAddress", deliveryAddress);
			if(updated == 0){
				//Try inserting
				updated = sqlSession.insert("insertDeliveryAddress", deliveryAddress);
			}
			if(updated == 0){
				dto.getMsgs().put(DTOMessageType.WARN, "DeliveryAddress ID = "+deliveryAddress.getDeliveryAddressId()+" could not be updated for Recipient ID "+recipient.getRecipientId());
				//Abort and let the close rollback
				return;
			}

			updated = sqlSession.update("updateRecipient", recipient);
			if(insert && updated == 0){
				//Try inserting only if insert is true
				updated = sqlSession.insert("insertRecipient", recipient);
			}
			if(updated == 0){
				dto.getMsgs().put(DTOMessageType.WARN, "Recipient ID = "+recipient.getRecipientId()+" could not be updated.");
				//Abort and let the close rollback
				return;
			}
		}
	}
	/**
	 * Will insert the Recipient , DeliveryAddress and Address
	 * in a single Transaction - rollback all if fail.
	 * @param dto
	 */
	public static void insertRecipient(UCSDto<Recipient> dto){
		SqlSession sqlSession = null;
		try {
			sqlSession = UCSDataConnection.getSqlSession();
			upsertRecipient(dto, sqlSession, true);

			//Check to see if we should commit
			if(	! (dto.getMsgs().containsKey(DTOMessageType.ERROR)
				|| dto.getMsgs().containsKey(DTOMessageType.WARN) )) {

				sqlSession.commit();
			}
		}
		catch(Exception e){
			dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for while updating Recipient. Please check your Recipient and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			log.error("******************* ERROR: Recipient update errored with - "+e.toString(), e);
		}
		finally{
			if(sqlSession!=null){
				sqlSession.close();
			}
		}
	}

	/*********** MESSAGE
	 * |: Message
	 */
	public static void insertMessage(UCSDto<? extends Message> dto)
	{
		for(Message baseMessage : dto.getListT()){
			MessageHeader messaageHeader = baseMessage.getHeader();
			UCSDto<MessageHeader> messageHeaderDto = new UCSDto<>();
			messageHeaderDto.getListT().add(messaageHeader);

			insertMessageHeader(messageHeaderDto);
			dto.getMsgs().putAll(messageHeaderDto.getMsgs());

			//Check to see if we should do MessageBody and ProcessingExceptions
			if(	! (dto.getMsgs().containsKey(DTOMessageType.ERROR)
					|| dto.getMsgs().containsKey(DTOMessageType.WARN) ))
			{
				Map<String,String> joinParams = new HashMap<>();
				SqlSession sqlSession = null;

				//Delete existing joins
				try{
					sqlSession = UCSDataConnection.getSqlSession();
					sqlSession.update("deleteMessageJoinProcessingExceptionForMessageId", messaageHeader.getMessageId());
					sqlSession.update("deleteMessageJoinMessageBodyForMessageId", messaageHeader.getMessageId());
				}
				catch(Exception e){
					log.error("******************* ERROR: MessageBody/Processing Exception Join delete errored with - "+e.toString(), e);
				}
				finally{
					if(sqlSession!=null){
						sqlSession.close();
					}
				}

				//Do the MessageBody(s)
				MessageBody[] parts = baseMessage.getParts();
				if(parts != null && parts.length>0){
					try{
						sqlSession = UCSDataConnection.getSqlSession();
						UCSDto<MessageBody> messageBodyDto;
						for(MessageBody part : parts){
							messageBodyDto = new UCSDto<>();
							messageBodyDto.getListT().add(part);
							upsertMessageBody(messageBodyDto, sqlSession, true);
							//Check to see if we should commit
							if(	(messageBodyDto.getMsgs().containsKey(DTOMessageType.ERROR)
									|| messageBodyDto.getMsgs().containsKey(DTOMessageType.WARN) )) {

								dto.getMsgs().putAll(messageHeaderDto.getMsgs());
								throw new PersistenceException("MessageBody '"+part.getMessageBodyId()+"' for Message '"+part.getMessageId()
										+"' could not be persisted!");
							}

							//Do the join
							joinParams.clear();
							joinParams.put("messageId",baseMessage.getHeader().getMessageId());
							joinParams.put("messageBodyId",part.getMessageBodyId());
							if(1 == sqlSession.update("insertMessageJoinMessageBody", joinParams)) {
								//Finally
								sqlSession.commit();
							}
						}
					}
					catch(Exception e){
						log.error("******************* ERROR: MessageBody update errored with - "+e.toString(), e);
					}
					finally{
						if(sqlSession!=null){
							sqlSession.close();
						}
					}
				}

				List<ProcessingException> exceptions = baseMessage.getExceptions();
				sqlSession = null;
				try{
					sqlSession = UCSDataConnection.getSqlSession();
					UCSDto<ProcessingException> exceptionDto;
					for(ProcessingException exception : exceptions) {
						exceptionDto = new UCSDto<>();
						exceptionDto.getListT().add(exception);
						upsertProcessingException(exceptionDto, sqlSession, true);
						dto.getMsgs().putAll(exceptionDto.getMsgs());
						//Check to see if we should commit
						if(	(exceptionDto.getMsgs().containsKey(DTOMessageType.ERROR)
								|| exceptionDto.getMsgs().containsKey(DTOMessageType.WARN) )) {

							dto.getMsgs().putAll(messageHeaderDto.getMsgs());
							throw new PersistenceException("ProcessingException '"+exception.getProcessingExceptionId()+"' for Message '"
									+baseMessage.getHeader().getMessageId()+"' could not be persisted!");
						}
						//Do the join
						joinParams.clear();
						joinParams.put("messageId",baseMessage.getHeader().getMessageId());
						joinParams.put("processingExceptionId",exception.getProcessingExceptionId());
						if(1 == sqlSession.update("insertMessageJoinProcessingException", joinParams)) {
							//Finally
							sqlSession.commit();
						}

					}
				}
				catch(Exception e){
					log.error("******************* ERROR: ProcessingException update errored with - "+e.toString(), e);
				}
				finally{
					if(sqlSession!=null){
						sqlSession.close();
					}
				}
			}
		}
	}
	public static void updateMessage(UCSDto<? extends Message> dto)
	{
		for(Message baseMessage : dto.getListT()){
			MessageHeader messaageHeader = baseMessage.getHeader();
			UCSDto<MessageHeader> messageHeaderDto = new UCSDto<>();
			messageHeaderDto.getListT().add(messaageHeader);

			updateMessageHeader(messageHeaderDto);
			dto.getMsgs().putAll(messageHeaderDto.getMsgs());
		}
	}
	@SuppressWarnings("unchecked")
	public static <T extends Message> void selectMessage(UCSDto<T> dto)
	{
			UCSDto<MessageHeader> simpleDto = new UCSDto<>();
			simpleDto.getAdHocParams().putAll(dto.getAdHocParams());

			selectMessageHeader(simpleDto, MessageHeaderSelectType.BYMSGID);
			dto.getMsgs().putAll(simpleDto.getMsgs());

			if(simpleDto.getListT().size()>0) {

				// Get the MessageBody and ProcessingExceptions
				String messageHeaderId = simpleDto.getListT().get(0).getMessageHeaderId();
				UCSDto<MessageBodyExceptionsDelegate> msgExDelegate = new UCSDto<>();
				msgExDelegate.getAdHocParams().put("messageId",messageHeaderId);
				selectMessageBodyExceptionsDelegateByMessageId(msgExDelegate);
				MessageBodyExceptionsDelegate delegate = null;
				if(msgExDelegate.getListT().size()>0){
					delegate = msgExDelegate.getListT().get(0); //should only be one
				}

				MessageType messageType = simpleDto.getListT().get(0).getMessageType();
				switch(messageType)
				{
					case SimpleMessage:
						for(MessageHeader header : simpleDto.getListT()) {
							SimpleMessageHeader simpleHeader = (SimpleMessageHeader)header;
							SimpleMessage message = new SimpleMessage(simpleHeader);
							if(delegate != null){
								message.setExceptions(delegate.getExceptionsList());
								message.setParts(delegate.getPartsArray());
							}
							dto.getListT().add((T)message);
						}
						break;

					case Notification:
						for(MessageHeader header : simpleDto.getListT()) {
							NotificationMessageHeader notificationHeader = (NotificationMessageHeader)header;
							NotificationMessage message = new NotificationMessage(notificationHeader);
							if(delegate != null){
								message.setExceptions(delegate.getExceptionsList());
								message.setParts(delegate.getPartsArray());
							}
							dto.getListT().add((T)message);
						}
						break;

					case ConversationRequest:
						for(MessageHeader header : simpleDto.getListT()) {
							ConversationRequestMessageHeader conversationHeader = (ConversationRequestMessageHeader)header;
							ConversationRequestMessage message = new ConversationRequestMessage(conversationHeader);
							if(delegate != null){
								message.setExceptions(delegate.getExceptionsList());
								message.setParts(delegate.getPartsArray());
							}
							dto.getListT().add((T)message);
						}
						break;

					case Alert:
						for(MessageHeader header : simpleDto.getListT()) {
							AlertMessageHeader alertHeader = (AlertMessageHeader)header;
							AlertMessage message = new AlertMessage(alertHeader);
							if(delegate != null){
								message.setExceptions(delegate.getExceptionsList());
								message.setParts(delegate.getPartsArray());
							}
							dto.getListT().add((T)message);
						}
						break;

					default:break;
				}
			}
	}

	/*********** MESSAGE BODY EXCEPTION DELEGATE
	 * |: MessageBodyExceptionDelegate
	 */
	public static void selectMessageBodyExceptionsDelegateByMessageId(UCSDto<MessageBodyExceptionsDelegate> dto){
		MessageBodyExceptionsDelegate delegate = null;
		String id = dto.getAdHocParams().get("messageId");
		SqlSession sqlSession = null;
		try{
			sqlSession = UCSDataConnection.getSqlSession();
			delegate = sqlSession.selectOne("selectMessageBodyAndExceptionsByMessageId", id);
			if(delegate == null){
//				dto.getMsgs().put(DTOMessageType.WARN, "An MessageBodyExceptionsDelegate for MessageHeader ID = "+id+" could not be found.");
			}
			else {
				dto.getListT().add(delegate);
			}
		}
		catch(Exception e){
			dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for MessageBody / Exceptions delegate ID = "+id+". Please check your parameters and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			log.error("******************* ERROR: ID = "+id+" errored with - "+e.toString(), e);
		}
		finally{
			if(sqlSession!=null){
				sqlSession.close();
			}
		}
	}

	/*********** PROCESSING EXCEPTION
	 * |: ProcessingException
	 */
	public static void selectProcessingExceptionById(UCSDto<ProcessingException> dto){
		ProcessingException processingException = null;
		String id = dto.getAdHocParams().get("processingExceptionId");
		SqlSession sqlSession = null;
		try{
			sqlSession = UCSDataConnection.getSqlSession();
			processingException = sqlSession.selectOne("selectProcessingExceptionById", id);
			if(processingException == null){
				dto.getMsgs().put(DTOMessageType.WARN, "A ProcessingException for ID = "+id+" could not be found.");
			}
			else {
				dto.getListT().add(processingException);
			}
		}
		catch(Exception e){
			dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for ProcessingException ID = "+id+". Please check your parameters and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			log.error("******************* ERROR: ProcessingException ID = "+id+" errored with - "+e.toString(), e);
		}
		finally{
			if(sqlSession!=null){
				sqlSession.close();
			}
		}
	}
	public static void updateProcessingException(UCSDto<ProcessingException> dto){
		SqlSession sqlSession = null;
		try{
			sqlSession = UCSDataConnection.getSqlSession();

			upsertProcessingException(dto, sqlSession, true);

			//Check to see if an ERROR or WARN was generated
			if(	dto.getMsgs().containsKey(DTOMessageType.ERROR)
				|| dto.getMsgs().containsKey(DTOMessageType.WARN) ) {

				dto.getMsgs().putAll(dto.getMsgs());
				//Abort and let the close rollback
				return;
			}
			else{
				//Get any INFO messages that might be generated
				dto.getMsgs().putAll(dto.getMsgs());
			}
			sqlSession.commit();
		}
		catch(Exception e){
			dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for while updating ProcessingException. Please check your ProcessingException and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			log.error("******************* ERROR: MessageBody update errored with - "+e.toString(), e);
		}
		finally{
			if(sqlSession!=null){
				sqlSession.close();
			}
		}
	}
	public static void insertProcessingException(UCSDto<ProcessingException> dto){
		SqlSession sqlSession = null;
		try{
			sqlSession = UCSDataConnection.getSqlSession();

			upsertProcessingException(dto, sqlSession, true);

			//Check to see if an ERROR or WARN was generated
			if(	dto.getMsgs().containsKey(DTOMessageType.ERROR)
				|| dto.getMsgs().containsKey(DTOMessageType.WARN) ) {

				dto.getMsgs().putAll(dto.getMsgs());
				//Abort and let the close rollback
				return;
			}
			else{
				//Get any INFO messages that might be generated
				dto.getMsgs().putAll(dto.getMsgs());
			}

			sqlSession.commit();
		}
		catch(Exception e){
			dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for while inserting ProcessingException. Please check your ProcessingException and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			log.error("******************* ERROR: ProcessingException update errored with - "+e.toString(), e);
		}
		finally{
			if(sqlSession!=null){
				sqlSession.close();
			}
		}
	}
	/**
	 * Will update ProcessingException
	 * If insert == true then if the ProcessingException does not exist it will be inserted.
	 * NOTE: IMPORTANT: The SqlSession and Transaction will need to be  controlled by the caller
	 * @param dto
	 */
	public static void upsertProcessingException(UCSDto<ProcessingException> dto, SqlSession sqlSession, boolean insert){
		try{
			for(ProcessingException processingException : dto.getListT()) {
				int updated = sqlSession.insert("updateProcessingException", processingException);
				if(insert && updated == 0 ){
					updated = sqlSession.insert("insertProcessingException", processingException);
				}
				if(updated == 0){
					dto.getMsgs().put(DTOMessageType.WARN, "ProcessingException ID = "+processingException.getProcessingExceptionId()+" could not be inserted.");
					//Abort and let the close rollback
					return;
				}
			}
		}
		catch(Exception e){
			dto.getMsgs().put(DTOMessageType.ERROR, "An error has occurred for while inserting MessageBody. Please check your MessageBody and resubmit."+
					" If you continue to receive errors please advise your system administrator.");
			log.error("******************* ERROR: MessageBody update errored with - "+e.toString(), e);
		}
	}

}

redpajama_set_name: RedPajamaGithub


text:
BIG by Matt Stoller
Will Spotify Ruin Podcasting?
mattstoller.substack.com
Matt Stoller
Welcome to BIG, a newsletter about the politics of monopoly. If you'd like to sign up, you can do so here. Or just read on…
Today I'm going to write about podcasting, with some observations on how a media market that is far healthier than online publishing functions. But first, a quick update on the Democratic Party cartel . Most people saw the fiasco of the Iowa caucuses as a political story, which it obviously is. But it's also a monopoly story. The corporation, ACRONYM, which ruined the caucuses with their Shadow app, has power because of their relationship with big donors. This nugget in Politico drives home the point.
Critics of ACRONYM declined to speak on the record because they are concerned about maintaining party unity and retribution from the group's donors.
Imagine that, fellow practitioners are so afraid of retaliation that they can't openly criticize a self-dealing con artist who openly ruined the Iowa caucuses, a sort of 737 Max moment for Democrats. This is the same fear and coercion we feel in any concentrated industry, only applied to the business of Democratic politics.
And now…
Photo credit to https://nicolassolop.com/.
A Functional Market
From 2000-2006, the web was an open place. If you built a highly trafficked web property, you could finance yourself by selling advertising. It wasn't just that the internet itself was a decentralized network, so were financing streams behind content. There were many publishers and paths for readers to get to those publishers through different search engines or aggregators, and ad networks matched advertisers to those properties. The result was a flourishing of voices and new media projects.
It was a three tiered system, with production, distribution, and advertising operating in vertically separated layers. There were certainly industry players who breached these layers - the New York Times sold its own ads and distributed its own product, but no one had dominant power in any one layer.
That is very much what podcasting looks like today. About a third of Americans listen to podcasts regularly, and there are 750,000 active podcasters, including independent successes like Chapo Trap House, The Bill Simmons Podcast and Joe Rogan, as well investment by major newspapers like the New York Times, and capital-infused podcast-only networks. Nonprofits have podcasts, so do industry associations.
There are real and serious problems in podcasting, as there are in any industry, leading to union drives and sometimes to attempts to ruin the industry by overfunded Wall Street goons. As more ad money floods into the space and branded podcasts become more important, there are debates over labor relationships, as well as censorship and advertising especially within large branded podcast institutions, like Pineapple Street Studios or Radiotopia or Spotify. Fear of Apple, which has a powerful spot in podcast distribution through its podcast app as well as its app store, is rife.
But these are debates within a growing industry in which various stakeholders debate values and rules. It's nothing like the nuclear winter of the non-audio internet content industry.
In podcasting, the major directories for distribution use an open standard called RSS to list podcasts, a standard originally developed by open web advocates like Aaron Swartz. Apple has the dominant podcast app. I've heard there are some issues with how Apple deals with ratings, but so far, Apple operates as a benevolent despot, largely not collecting data and not privileging its own content. There are also a host of advertising networks, as well as some subscription options, so financing is relatively distributed.
Podcasting is a three tiered system, with production, distribution, and advertising in vertically separated layers. There are corporations who produce, distribute, and sell podcast ads, but the markets in all three layers are open. As a result, there's relative ease of entry. It's hard to build an audience, but if you do, you can get access to a financing channel. And the net result is a lot of diverse voices and entrepreneurship. This dynamic is changing somewhat, as branded podcast corporations increasingly build formulas to launch new shows, but relative to near all other media ecosystems, with the exception of the mid-2000s open internet, it's open. And the debates within the industry about contested relationships between advertisers, podcast hosts, corporate management, and workers happen within a context of a relatively flexible system.
There may be different interest groups within the industry, but the industry as a whole has an interest in preserving this structure. It was very much like the open web of the mid-2000s. So there are many lessons podcast industry players can learn from what happened to that space, and how to prevent it from happening to podcasting.
Why the Open Web and Publishing Began Dying
Anyone trying to understand modern media or monopoly has to spend a lot of time understanding Google and Facebook, because they are the pace-setters in our economy. Every corporate leader, from agricultural to podcasting, sees what they have done, and tries to reproduce their success in their own industry.
While Google and Facebook are framed as tech companies, they are in fact advertising companies and middlemen in the flow of information. Google gets roughly 80% of its revenue from ads, for Facebook it's a little over 98%. And what they did from 2004-2014 was to redirect the flow of ad money from publishers to themselves. So that's what's happening throughout the economy, especially in the media industry; middlemen everywhere are trying to find ways to redirect the flow of other people's revenue to themselves.
Google and Facebook did this through two key techniques. The first was to acquire gatekeeping power in distribution. Google is gatekeeper in search, online video, and maps, whereas Facebook is a gatekeeper in social networking. To get to users, you have to go through Google and Facebook.
The second was to use this gatekeeping power to vertically integrate into dominant advertising platforms. Google and Facebook both sell large amounts of advertising, and they both bought up adtech companies in a merger spree from 2004-2014. They force partners to hand over data - and data is a key input into advertising - as a condition of getting access to their networks.
Such data wasn't just personal information about the users, but more importantly, proprietary business data of publishers. The Wall Street Journal has a list of subscribers and readers, and it would never share this information with the New York Times, because they are competing to sell space to the same advertisers. But both Google and Facebook got all of this proprietary information, and more, from the publishers using their networks.
With most users going through either Google properties or Facebook properties, these corporations could then de-commodify the ad value of a publisher ad slot. After these corporations became dominant, there simply was no need to advertise in, say the New York Times to show an ad to a New York Times reader if Google could show that ad to that same wealthy reader cheaper somewhere else. The relationship between the New York Times and the reader is based on trust earned through capital invested in reporting the news. Google then takes that relationship in the form of structured data, and makes money that should have gone to the New York Times by advertising to New York Times readers against non-New York Times content. This looks more like a form of theft, not efficiency.
Facebook and Google dominance is across an entire layer - search or social - and also vertically upstream into the advertising, and increasingly the distribution through apps and browsers.
It's important to recognize that these corporations shape their business models around public policy decisions that enable monopolization and targeted ads. Both Facebook and Google acquired power a series of anti-competitive mergers, like Google's purchase of YouTube and DoubleClick, Facebook's purchase of Instagram and WhatsApp. Similarly, during debates over privacy and data in the 1990s and 2000s, libertarians won the argument that data markets should remain largely free of public rules. And so these two platforms, who were running communications networks that should have been regulated as public utilities, could discriminate and insert ads in front of those who needed to use their networks to reach customers.
The result of these public policy decisions was Google and Facebook, the death of the open web, and increasingly, independent publishing on the internet.
So what lessons does that hold for the podcasting industry?
The Spotify Challenge
This week, Spotify announced an acquisition of The Ringer, one of the biggest networks in podcasting, built by sports writer Bill Simmons, with 30 different podcasts that have hundreds of millions of downloads a month. The CEO of Spotify discussed what they see in terms of value with this acquisition, saying "we bought the next ESPN." It's an open question what Spotify is going to do with this acquisition, but the strategy of the corporation seems fairly clear.
Spotify is rolling up the internet audio market, which their CEO, Daniel Ek, thinks will grow from $100 billion in revenue a year to somewhere near the video market, which is $1 trillion. People spend about the same amount of time consuming audio as video content. "Are our eyes," he asks, "really worth 10 times more than our ears?"
From 2014 to 2020, Spotify bought 15 companies, companies that build everything from data analytics to music and audio production tools to audio ad tools to licensing platforms, and podcasting networks. These companies included the Echo Nest (2014), Seed Scientific (2015), CrowdAlbum (2016), Sonalytic (2017), MightyTV (2017), Mediachain (2017), Niland (2017), SoundTrap (2017), Loudr (2018), Gimlet (2019), Anchor (2019), SoundBetter (2019), Parkast (2019), and now The Ringer (2020).
Spotify is directly mimicking Google and Facebook, and attempting to roll up power over digital audio markets the way Google and Facebook did over the internet. It has already done so in music. Here's Rolling Stone, reporting on Spotify's exploitation of its public utility platform of music distribution to organizes payola-style extortion against artists.
In Spotify's pitch deck, which was sent to a distribution company, the platform advises artists or labels to spend at least $5,000 on these campaigns. If an artist pays that amount at 55 cents a click, the campaign should bring in more than 9,000 potential listeners over a seven-day period.
The streaming service's announcement immediately drew comparisons to pay-for-play at radio or, maybe more accurately, the retail variation, where record shops asked labels to pay for premium positioning. "Stores would say, 'we want you to pay us for this space,'" explains George Howard, a professor of music business and management at Berklee College of Music. "Spotify is saying, 'we want you to pay us to display your records.' All this does is continue what payola always has done — the major labels, which have the most money and the most frequent releases, get the most play, consolidating the amount of art that is put out there."
Payola of this sort tends to work against small players, and cartelize industries into giants negotiating with one another. Will that happen in podcasting? Spotify is likely seeking to make it so, especially since it went public and has to answer to investors who want a story about the company's strategy, which is a coded way to talk about acquiring market power. Last year, Spotify bought several podcasting companies, including podcasting network Gimlet Media Inc. ("Gimlet"), as well as Anchor, which made podcast creation and ad tools. Ek made it clear during this acquisition that the goal of Spotify is to dominate podcasting. Here's what he wrote in his blog post explaining the acquisitions.
No other company has Spotify's scale and audience around the globe with the 78 markets where we do business. No other audio company has the two-sided marketplace that we have built at Spotify — a marketplace that benefits artists and creators along with consumers. Nobody else has both audio advertising and subscription revenue model at scale globally. Nobody else in music has the engineering capabilities and the expertise in audio that we have at Spotify. And with the addition of Gimlet and Anchor, Spotify will now become the leading global podcast publisher with more shows than any other company.
Podcasting, he argues, can become a business based on scale and vertical integration. "As we expand deeper into audio, especially with original content," he wrote, "we will scale our entire business, creating leverage in the model through subscriptions and ads." The business can "become significantly larger when you add Internet-level monetization to it." Ek isn't really being clear about what he means by scale. Podcasting, with its relatively decentralized structure, is already a scale business. RSS is an open standard and tens of millions of people listen to podcasts.
What Ek really means is that he's trying to privatize the open standards, basically take over the already large public commons. To the extent there is scaling going on, it's just doing sales work to move ad budgets from big companies in other audio or video channels to Spotify. This is scaling like Google and Facebook did scale, they took over an already massive web organized by open standards and democratic participation, and pretended they built it so they could control it.
Ek also added typical language for aspiring dominant players, which is pablum on how there's "fierce competition" in podcasting and how his company believes in a level playing field. Such language is almost always inserted into merger acquisition press releases by antitrust lawyers, and is a clear tell, like a kid with crumbs on his face preemptively telling his parents "I didn't eat any cookies before dinner."
Commodifying Independent Podcasts
Recently, Spotify announced a new advertising service, Spotify Podcast Ads with what's called 'streaming ad insertion.' Here's what it means.
The technology makes key data — like actual ad impressions, frequency, reach, plus anonymized age, gender and device type — available to podcasters and advertisers for the first time.
In previous years, podcasts have been delivered by way of downloads from RSS feeds, which would make this sort of data collection difficult if not impossible. The shift to streaming changes that, as Spotify can tap into its suite of planning, reporting and measurement capabilities, as it does for streaming music.
In other words, Spotify is doing what Google and Facebook did. First, it is privatizing the distribution standard for podcasts by converting more people to streaming their podcasts through Spotify services instead of the open RSS standard. That means it is becoming a gatekeeper to consumers. Second, it is vertically integrating into advertising, and developing the technology to grab data that podcasts generate through their trusted relationship with listeners. It then uses that for targeting.
Spotify seems to be trying to build out control of advertising and distribution of podcasting. With gatekeeping power over listeners through its streaming service, and gatekeeping power over ad revenue through its advertising network, Spotify will eventually be able to force podcasters to live in its ecosystem. It won't be impossible to get listeners without Spotify, just as newspapers can technically get direct traffic instead of traffic from Google and Facebook, but it'll be very hard.
It's not 100% clear how well this will work. Podcast ads are, like radio ads, often based on the trust of the host of the show. Hosts tend to read out ads, so they are more like paid sponsorships relying on the trust and voice of the host than traditional advertisements. But if the Spotify ad insertion tech is sophisticated and effective enough, they may be able to alter this dynamic, making any particular ad slot on any podcast less valuable.
No advertiser will care if you're a listener of Joe Rogan or Bill Simmons, only that you are a 34 year old male with a certain income reachable in thirty forty different audio slots, which can then all go in an auction. Or even if they do care, competitive ad networks who offer the service you want will probably die. Then, just as the New York Times content becomes far less important online because Google can just find you that New York Times reader through another publisher outlet or Google's own properties, the actual podcast becomes commodified, because all that matters is the listener data combined with the ad slots, not the show against which those ad slots are sold. This is another complicated way of saying the people who do the work of making and distributing a show don't get the benefit from the work they do.
What happens in this scenario is that a few giants, not just Spotify, ultimately become dominant vertically integrated podcast, distribution and advertising platforms. These will then cut deals with each other, and cartelize the industry into a land of giants, much as Disney is doing in Hollywood. (In fact, I wouldn't be surprised to see these two spaces merge.)
The Clayton Act and Incipiency Standard
None of this is inevitable. And in fact, there's one significant difference between today and the mid-2000s, and it's not a small one. Today, we understand that monopolies are dangerous. This means we can activate a whole series of laws designed to protect us from them.
Fortunately, there is a law to address the merger spree Spotify is engaging in, and it's called the Clayton Act. Passed in 1913 and strengthened in 1950, the idea is to stop monopolies through mergers, even when such monopolies are in their incipiency. A lot of states have similar laws against mergers. Similarly, states and Congress and foreign regulators are debating privacy and data laws, and what is this Spotify problem if not a privacy and data problem? Podcasters, if they chose to organize around this market structure problem, could have substantial political power.
Today's flourishing podcasting market is evidence that we can have a diverse and financially viable media market. We just have to stop gatekeeping in our markets for speech, prevent vertical integration, end the inappropriate use of other people's data to let a middleman like Spotify inappropriately monetize art it didn't create, and retain open standards like RSS for podcast distribution instead of privatizing public utility functions.
Oh, and it's probably a good idea to tell the Federal Trade Commission to block the Ringer merger. They can do that, you know.
Thanks for reading. And if you liked this essay, you can sign up here for more issues of BIG, a newsletter on how to restore fair commerce, innovation and democracy. If you want to really understand the secret history of monopoly power, read my book, Goliath: The 100-Year War Between Monopoly Power and Democracy.
P.S. Here's an email on CVS's wonderful 'integration' of its different lines of business.
I have an additional tactic CVS/Healthcare companies recently employed to get even more money out of those of us unfortunate enough to be sick. I take a specialty pharmacy drug for an auto-immune disease. There is no cure but this medicine greatly improves my quality of life and allows me to function somewhat normally and be gainfully employed rather than on benefits. It is a very expensive drug, taken monthly so I factor that into my decision when picking a healthcare plan on the marketplace. Fortunately, the drug manufacturer offers a rebate program that can reduce the cost of that drug from $5000+ refill to $5. For years, I was able to put that co-pay assistance card on file with the pharmacy and they would bill that directly. My co-pay would be hit for the year and I could rest a little easier knowing a hospital visit would be covered.
A few years ago, the insurance company stopped applying co-pay assistance payments toward my deductible. When I called to ask how they would even know that I had paid for a prescription that was fulfilled by the pharmacy using a co-pay assistance card, which they previously should have had no knowledge even existed, they responded that the pharmacy managed all their prescription drug benefits now so the information was "in-house" so to speak. Want to take a guess who the companies were? CVS Caremark and Blue Cross Blue Shield. For the record, on the South Carolina marketplace that year, BCBS was the only healthcare company offering plans through healthcare.gov. This is not a shot at the ACA, which helps me greatly by ensuring I cannot be denied coverage for my preexisting condition, but the idea of calling something a "marketplace" when there is only one company offering plans is ludicrous.
I am fortunate enough to have good credit and a way to absorb those ridiculous drug costs every year but I am sure millions of Americans cannot just put $5000 on a credit card and pay it off as over the course of the year. On a side note, I often wonder how much of the "drug cost" I pay goes toward the expense of commercials for the drug I see every Sunday during NFL games?
Thanks for your time and I really enjoyed your book. I tell everyone I know to read it so they can learn about the bi-partisan efforts to erode small business in America.
Commenting has been turned off for this post
Feb 8, 2020Author
Comments are enabled, so you can talk about this post if you want.
Marcos H
There was good discussion on the "facebooking" of podcasting when Luminary was rolling out on the Accidental Tech Podcast, Epsiode 323. https://atp.fm/episodes/323
The general hope has been that as long as there are a) podcasters who want to stay independent, b) podcast apps that want to stay independent (or stay aloof to monetization like Apple Podcasts), and c) advertisers who want to advertise on the podcasts made by a) then the podcast work should be fine. Maybe the big media podcasts like Ringer and/or maid by celebrities go exclusively to Spotify or Luminary lured in by some big upfront paycheck. But presumably indie podcasts stay with RSS, and someone makes apps that play those, and things should stay ok. Hopefully.
But yes for sure, I oppose all mergers, including this one.
Gift a subscriptionCollapse
© 2023 Matt Stoller

redpajama_set_name: RedPajamaCommonCrawl


text:
[[missing key: search-facet.tree.open-section]] Art and architecture (17)
[[missing key: search-facet.tree.open-section]] architecture and landscape (2)
architectural planner (1)
land surveyor (1)
[[missing key: search-facet.tree.open-section]] art (general) (2)
technical innovator in art (2)
[[missing key: search-facet.tree.open-section]] ceramics and glass (1)
glassmaker or glass artist (1)
[[missing key: search-facet.tree.open-section]] painting and drawing (3)
landscape painter (2)
[[missing key: search-facet.tree.open-section]] patrons, collectors, and dealers (5)
art collector (2)
patron of the arts (3)
architectural historian (2)
art historian (1)
[[missing key: search-facet.tree.open-section]] Law and crime (17)
[[missing key: search-facet.tree.open-section]] lawyers (17)
lawyer (general) (9)
solicitor (2)
[[missing key: search-facet.tree.open-section]] colonial administration (1)
colonial official (1)
[[missing key: search-facet.tree.open-section]] chemistry (2)
chemist (2)
[[missing key: search-facet.tree.open-section]] literary patronage (1)
literary patron (1)
[[missing key: search-facet.tree.open-section]] Christianity (12)
[[missing key: search-facet.tree.open-section]] Catholic (2)
Unitarian (1)
Brett, Sir Charles Edward Bainbridge (1928–2005), solicitor and architectural historian
Robert Carswell
Brett, Sir Charles Edward Bainbridge (1928–2005), solicitor and architectural historian, was born on 30 October 1928 at Glenard, Holywood, co. Down, the eldest son of Charles Anthony Brett (1897–1988), solicitor, and his wife, (Elizabeth) Joyce, née Carter. His father was senior partner of ...
Brett, Sir Charles Edward Bainbridge (1928–2005)
Maker: Jill Jennings
Sir Charles Edward Bainbridge Brett (1928–2005) by Jill Jennings Country Life Picture Library
Churchyard, Thomas (1798–1865), landscape painter and lawyer
Denis Thomas
Churchyard, Thomas (1798–1865), landscape painter and lawyer, was born on 22 January 1798 at the family home, a butcher's shop, opposite the Horse and Groom in the village of Melton, Suffolk, the only child of Jonathan Churchyard (1765–1825), cattle dealer, grazier, and butcher, and his wife, ...
Disney, John (1779–1857), barrister and art collector
David Gill
Disney, John (1779–1857), barrister and art collector, born at Flintham Hall, Nottinghamshire, the home of his paternal uncle Lewis Disney, on 29 May 1779, was the eldest son of seven children of the Revd John Disney DD (1746–1816), a Church of England, and later Unitarian, clergyman, and his wife, ...
Dowson, Sir Ernest MacLeod (1876–1950), colonial administrator and expert on land registration
Martin Bunton
Dowson, Sir Ernest MacLeod (1876–1950), colonial administrator and expert on land registration, was born on 19 November 1876 in India, the only son of Ernest Dowson, superintendent of the Indian government telegraph department. He was educated at the Isle of Wight College and the ...
Duane, Matthew (1707–1785), lawyer and art patron
D. G. C. Allan
Duane, Matthew (1707–1785), lawyer and art patron, was of obscure family origins. It seems likely that he came from Ireland and that his education took place in a Catholic school on the continent. Prevented by his religion from being called to the bar, he was by his mid-thirties practising with great success as a 'chamber counsel' and conveyancer in ...
Duane, Matthew (1707–1785)
Maker: James Mitan
Matthew Duane (1707–1785) by James Mitan, pubd 1798 (after Giles Hussey, 1774) © National Portrait Gallery, London
Fielding, Felix Ferdinand Frederick Raffael (c. 1784–1853)
L. H. Cust
Fielding, Newton Smith Limbird (1799–1856), watercolour painter and lithographer
See Fielding, Newton Smith Limbird
Pearson, Charles (1793–1862), lawyer and urban reformer
Michael Robbins
Pearson, Charles (1793–1862), lawyer and urban reformer, was born on 4 October 1793 at 25 Clement's Lane, London, son of Thomas Pearson, upholsterer and feather merchant, and his wife, Sarah. Educated at Eastbourne, Sussex, he was initially apprenticed to his father, but was admitted solicitor in 1816, and developed an extensive practice. He was made free of the ...
Ponton, Mungo (1802–1880), inventor of photographic processes and lawyer
R. B. Prosser
revised by A. D. Morrison-Low
Ponton, Mungo (1802–1880), inventor of photographic processes and lawyer, the only son of John Ponton, farmer, and his wife, Isabell née Ponton, was born at Balgreen, near Edinburgh, on 23 November 1802. Educated as a lawyer, he was admitted to the Society of Writers to the Signet...
Ponton, Mungo (1802–1880)
Maker: unknown photographer
Mungo Ponton (1802–1880) by unknown photographer, 1870s Heritage Images Partnership
Taverner, William (1700–1772), lawyer and landscape painter
Andrew Wyld and Susan Sloman
Taverner, William (1700–1772), lawyer and landscape painter, was born on 25 November 1700 in London, and baptized the same day at St Martin Ludgate, the son of William Taverner (d. 1731), lawyer and dramatist, and his wife, Alathea Taverner, and grandson of Jeremiah Taverner...
Theobald, Sir Henry Studdy (1847–1934), barrister and art collector
Sheila Doyle
Theobald, Sir Henry Studdy (1847–1934), barrister and art collector, was born in Calcutta on 7 June 1847, the only son of William Theobald (1798–1870) and his second wife, formerly Mrs Balfour, née Bishop. Each parent had one child by a previous spouse. William Theobald...
Tooke, William (1777–1863), lawyer and promoter of arts and literature
G. C. Boase
revised by Eric Metcalfe
Tooke, William (1777–1863), lawyer and promoter of arts and literature, was born in St Petersburg, Russia, on 22 November 1777. He was the younger son of William Tooke (bap. 1744, d. 1820), historian of Russia and chaplain to the factory of the Russia Company...
Winston, Charles (1814–1864), barrister and stained-glass historian
revised by Stanley A. Shepherd
Winston, Charles (1814–1864), barrister and stained-glass historian, was born on 10 March 1814 at Lymington, Hampshire, the elder of the two sons of the Revd Benjamin Winston, rector of Farningham, Kent, and his wife, Helen, daughter of Sir Thomas Reid, baronet. His father, whose original name was ...


text:
Home > Big Story > Coronavirus India updates: Death toll rises to 242 as cases cross 7,500 mark
Coronavirus India updates: Death toll rises to 242 as cases cross 7,500 mark
Coronavirus (COVID-19): A total of 1035 positive novel coronavirus (COVID-19) cases and 40 deaths have been detected in the last 24 hours taking the total number of overall cases to 7,529 in India, the Union Health Ministry said in its latest briefing on Saturday. Most states, as per government sources, have requested Prime Minister Narendra Modi to extend the nationwide lockdown beyond April 14 for two more weeks. "The central government is considering this request," government sources said following the meeting between the PM and chief ministers via video conferencing.
Emphasising on the impact of the lockdown, the health ministry further said that after restrictions were imposed, the growth rate of infections is down to 29 per cent. It would have been 45,000 without lockdown and would have been 2,00,000 without any measures," a health ministry official stated.
The total number of confirmed cases due to the novel coronavirus rose to 7,527, including 652 recoveries and 242 deaths. The highest number of infections have been reported from Maharashtra (1,666), followed by Tamil Nadu (911), and New Delhi (903). Maharashtra has also reported the highest number of deaths (110) till date. Madhya Pradesh has the second largest share of deaths (33), followed by Gujarat (19).
Arvind Kejriwal says PM has taken 'correct decision to extend lockdown'
Immediately after Prime Minister's video conference with chief ministers of all states ended, Delhi Chief Minister Arvind Kejriwal said "PM has taken correct decision to extend lockdown".
"PM has taken correct decision to extend lockdown. Today, India's position is better than many developed countries only because we have started lockdown early. IF it is stopped now, all gains would be lost. To consolidate, it is important to extend it," he wrote on Twitter.
However, no official confirmation has come from the central government on this regard as of yet.
Odisha and Punjab governments have already taken a decision to extend restrictions in the two states till April 30 and May 1, respectively. Few other states and Union Territories like Delhi, Maharashtra, Bihar and West Bengal were also on board with the decision to extend the lockdown till month end.
Maharashtra govt extends lockdown till April 30
With Maharashtra being the worst affected state in India with 1,666 infections as of Saturday, Chief Minister Uddhav Thackeray announced an extension of the lockdown till April 30. "In this next lockdown, the restrictions will be relaxed in some places but it will be made more stringent in some other places," Thackeray said at a press briefing.
Meanwhile, the state Health Department reported 92 new cases of coronavirus today, taking the total number of infections in the state to 1,666. Mumbai reported 72 of the total 92 new cases, followed by Malegaon (5), Thane (4), and Aurangabad (2). Pune reported one new case.
Amid rising number of cases and deaths due to coronavirus in the state, Pune district administration has urged private hospital doctors to help formulate a "treatment protocol". District collector Naval Kishore Ram said the administration was working on a treatment protocol for COVID-19 patients and conducting an assessment of casualties so far, PTI reported.
(Inputs and image from theindianexpress.com)