Java tutorial
/* * Copyright (C) 2015 * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.cleverbus.core.common.dao; import org.apache.commons.lang.time.DateUtils; import org.apache.commons.lang3.StringUtils; import org.cleverbus.api.entity.ExternalSystemExtEnum; import org.cleverbus.api.entity.Message; import org.cleverbus.api.entity.MsgStateEnum; import org.cleverbus.api.exception.NoDataFoundException; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import javax.annotation.Nullable; import javax.persistence.EntityManager; import javax.persistence.LockModeType; import javax.persistence.PersistenceContext; import javax.persistence.TypedQuery; import java.sql.Timestamp; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.List; /** * JPA implementation of {@link MessageDao} interface. * * @author <a href="mailto:petr.juza@cleverlance.com">Petr Juza</a> */ @Repository public class MessageDaoJpaImpl implements MessageDao { public static final int MAX_MESSAGES_IN_ONE_QUERY = 50; @PersistenceContext(unitName = DbConst.UNIT_NAME) private EntityManager em; @Override @Transactional(propagation = Propagation.MANDATORY) public void insert(Message msg) { em.persist(msg); } @Override @Transactional(propagation = Propagation.MANDATORY) public void update(Message msg) { em.merge(msg); } @Override @Nullable public Message findMessage(Long msgId) { Assert.notNull(msgId, "the msgId must not be null"); return em.find(Message.class, msgId); } @Override @Nullable public Message findEagerMessage(Long msgId) { Assert.notNull(msgId, "the msgId must not be null"); TypedQuery<Message> q = em.createQuery("SELECT m FROM " + Message.class.getName() + " m " + " left join fetch m.externalCalls " + " left join fetch m.requests " + "WHERE m.msgId = :msgId", Message.class); q.setParameter("msgId", msgId); return q.getSingleResult(); } @Override public Message getMessage(Long msgId) { Assert.notNull(msgId, "the msgId must not be null"); Message msg = em.find(Message.class, msgId); if (msg == null) { throw new NoDataFoundException("no message with id: " + msgId); } return msg; } @Override public List<Message> findChildMessages(Message msg) { TypedQuery<Message> q = em.createQuery( "SELECT m FROM " + Message.class.getName() + " m WHERE m.parentMsgId = :parentMsgId", Message.class); q.setParameter("parentMsgId", msg.getParentMsgId()); return q.getResultList(); } @Override @Nullable public Message findByCorrelationId(String correlationId, @Nullable ExternalSystemExtEnum sourceSystem) { Assert.notNull(correlationId, "the correlationId must not be null"); String jSql = "SELECT m " + "FROM " + Message.class.getName() + " m " + "WHERE m.correlationId = :correlationId"; if (sourceSystem != null) { jSql += " AND m.sourceSystemInternal = :sourceSystem"; } TypedQuery<Message> q = em.createQuery(jSql, Message.class); q.setParameter("correlationId", correlationId); if (sourceSystem != null) { q.setParameter("sourceSystem", sourceSystem.getSystemName()); } // we search by unique key - it's not possible to have more records List<Message> messages = q.getResultList(); if (messages.isEmpty()) { return null; } else { return messages.get(0); // if find more items then return first one only } } @Override @Nullable public Message findPartlyFailedMessage(int interval) { // find message that was lastly processed before specified interval Date lastUpdateLimit = DateUtils.addSeconds(new Date(), -interval); String jSql = "SELECT m " + "FROM " + Message.class.getName() + " m " + "WHERE m.state = '" + MsgStateEnum.PARTLY_FAILED + "'" + " AND m.lastUpdateTimestamp < :lastTime" + " ORDER BY m.msgTimestamp"; TypedQuery<Message> q = em.createQuery(jSql, Message.class); q.setParameter("lastTime", new Timestamp(lastUpdateLimit.getTime())); q.setMaxResults(1); List<Message> messages = q.getResultList(); if (messages.isEmpty()) { return null; } else { return messages.get(0); } } @Override @Nullable public Message findPostponedMessage(int interval) { // find message that was lastly processed before specified interval Date lastUpdateLimit = DateUtils.addSeconds(new Date(), -interval); String jSql = "SELECT m " + "FROM " + Message.class.getName() + " m " + "WHERE m.state = '" + MsgStateEnum.POSTPONED + "'" + " AND m.lastUpdateTimestamp < :lastTime" + " ORDER BY m.msgTimestamp"; TypedQuery<Message> q = em.createQuery(jSql, Message.class); q.setParameter("lastTime", new Timestamp(lastUpdateLimit.getTime())); q.setMaxResults(1); List<Message> messages = q.getResultList(); if (messages.isEmpty()) { return null; } else { return messages.get(0); } } @Override public Boolean updateMessageForLock(final Message msg) { Assert.notNull(msg, "the msg must not be null"); // acquire pessimistic lock firstly String jSql = "SELECT m " + "FROM " + Message.class.getName() + " m " + "WHERE m.msgId = :msgId" + " AND (m.state = '" + MsgStateEnum.PARTLY_FAILED + "' " + " OR m.state = '" + MsgStateEnum.POSTPONED + "')"; TypedQuery<Message> q = em.createQuery(jSql, Message.class); q.setParameter("msgId", msg.getMsgId()); // note: https://blogs.oracle.com/carolmcdonald/entry/jpa_2_0_concurrency_and q.setLockMode(LockModeType.PESSIMISTIC_WRITE); Message dbMsg = q.getSingleResult(); if (dbMsg != null) { // change message's state to PROCESSING msg.setState(MsgStateEnum.PROCESSING); Date currDate = new Date(); msg.setStartProcessTimestamp(currDate); msg.setLastUpdateTimestamp(currDate); update(msg); } return true; } @Override public List<Message> findProcessingMessages(int interval) { final Date startProcessLimit = DateUtils.addSeconds(new Date(), -interval); String jSql = "SELECT m " + "FROM " + Message.class.getName() + " m " + "WHERE m.state = '" + MsgStateEnum.PROCESSING + "'" + " AND m.startProcessTimestamp < :startTime"; TypedQuery<Message> q = em.createQuery(jSql, Message.class); q.setParameter("startTime", new Timestamp(startProcessLimit.getTime())); q.setMaxResults(MAX_MESSAGES_IN_ONE_QUERY); return q.getResultList(); } @Override public int getCountMessages(MsgStateEnum state, Integer interval) { Date lastUpdateTime = null; String jSql = "SELECT COUNT(m) " + "FROM " + Message.class.getName() + " m " + "WHERE m.state = '" + state.name() + "'"; if (interval != null) { lastUpdateTime = DateUtils.addSeconds(new Date(), -interval); jSql += " AND m.lastUpdateTimestamp >= :lastUpdateTime"; } TypedQuery<Number> q = em.createQuery(jSql, Number.class); if (lastUpdateTime != null) { q.setParameter("lastUpdateTime", new Timestamp(lastUpdateTime.getTime())); } return q.getSingleResult().intValue(); } @Override public int getCountProcessingMessagesForFunnel(Collection<String> funnelValues, int idleInterval, String funnelCompId) { Assert.notEmpty(funnelValues, "funnelValues must not be empty"); Assert.hasText(funnelCompId, "funnelCompId must not be empty"); String jSql = "SELECT COUNT(m) " + "FROM " + Message.class.getName() + " m " + "INNER JOIN m.funnels f " + "WHERE (m.state = '" + MsgStateEnum.PROCESSING + "' " + " OR m.state = '" + MsgStateEnum.WAITING + "'" + " OR m.state = '" + MsgStateEnum.WAITING_FOR_RES + "')" + " AND m.startProcessTimestamp >= :startTime" + " AND m.funnelComponentId = '" + funnelCompId + "'" + " AND ("; for (String funnelValue : funnelValues) { jSql += "f.funnelValue = '" + funnelValue + "' OR "; } //remove last or jSql = StringUtils.substringBeforeLast(jSql, " OR "); jSql += ")"; TypedQuery<Number> q = em.createQuery(jSql, Number.class); q.setParameter("startTime", new Timestamp(DateUtils.addSeconds(new Date(), -idleInterval).getTime())); return q.getSingleResult().intValue(); } @Override public List<Message> getMessagesForGuaranteedOrderForRoute(Collection<String> funnelValues, boolean excludeFailedState) { Assert.notNull(funnelValues, "funnelValues must not be null"); if (funnelValues.isEmpty()) { return Collections.emptyList(); } else { //TODO (juza) limit select to specific number of items + add msgId DESC to sorting (parent vs. child) String jSql = "SELECT m " + "FROM " + Message.class.getName() + " m " + "INNER JOIN m.funnels f " + "WHERE (m.state = '" + MsgStateEnum.PROCESSING + "' " + " OR m.state = '" + MsgStateEnum.WAITING + "'" + " OR m.state = '" + MsgStateEnum.PARTLY_FAILED + "'" + " OR m.state = '" + MsgStateEnum.POSTPONED + "'"; if (!excludeFailedState) { jSql += " OR m.state = '" + MsgStateEnum.FAILED + "'"; } jSql += " OR m.state = '" + MsgStateEnum.WAITING_FOR_RES + "')" + " AND m.guaranteedOrder is true" + " AND ("; for (String funnelValue : funnelValues) { jSql += "f.funnelValue = '" + funnelValue + "' OR "; } //remove last or jSql = StringUtils.substringBeforeLast(jSql, " OR "); jSql += ") ORDER BY m.msgTimestamp"; TypedQuery<Message> q = em.createQuery(jSql, Message.class); return q.getResultList(); } } @Override public List<Message> getMessagesForGuaranteedOrderForFunnel(Collection<String> funnelValues, int idleInterval, boolean excludeFailedState, String funnelCompId) { Assert.notNull(funnelValues, "funnelValues must not be null"); Assert.hasText(funnelCompId, "funnelCompId must not be empty"); if (funnelValues.isEmpty()) { return Collections.emptyList(); } else { String jSql = "SELECT m " + "FROM " + Message.class.getName() + " m " + "INNER JOIN m.funnels f " + "WHERE (m.state = '" + MsgStateEnum.PROCESSING + "' " + " OR m.state = '" + MsgStateEnum.WAITING + "'" + " OR m.state = '" + MsgStateEnum.PARTLY_FAILED + "'" + " OR m.state = '" + MsgStateEnum.POSTPONED + "'"; if (!excludeFailedState) { jSql += " OR m.state = '" + MsgStateEnum.FAILED + "'"; } jSql += " OR m.state = '" + MsgStateEnum.WAITING_FOR_RES + "')" + " AND m.funnelComponentId = '" + funnelCompId + "'" + " AND m.startProcessTimestamp >= :startTime" + " AND ("; for (String funnelValue : funnelValues) { jSql += "f.funnelValue = '" + funnelValue + "' OR "; } //remove last or jSql = StringUtils.substringBeforeLast(jSql, " OR "); jSql += ") ORDER BY m.msgTimestamp"; //TODO (juza) limit select to specific number of items + add msgId DESC to sorting (parent vs. child) TypedQuery<Message> q = em.createQuery(jSql, Message.class); q.setParameter("startTime", new Timestamp(DateUtils.addSeconds(new Date(), -idleInterval).getTime())); return q.getResultList(); } } @Override public List<Message> findMessagesByContent(String substring) { Assert.hasText("the substring must not be empty", substring); String jSql = "SELECT m " + " FROM " + Message.class.getName() + " m " + " WHERE (m.payload like :substring) ORDER BY m.msgId DESC"; TypedQuery<Message> q = em.createQuery(jSql, Message.class); q.setParameter("substring", "%" + substring + "%"); q.setMaxResults(MAX_MESSAGES_IN_ONE_QUERY); return q.getResultList(); } }