/** * Licensed to The Apereo Foundation under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * * The Apereo Foundation licenses this file to you under the Educational * Community License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License * at: * * http://opensource.org/licenses/ecl2.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * */ package org.opencastproject.usertracking.impl; import org.opencastproject.usertracking.api.Footprint; import org.opencastproject.usertracking.api.FootprintList; import org.opencastproject.usertracking.api.Report; import org.opencastproject.usertracking.api.ReportItem; import org.opencastproject.usertracking.api.UserAction; import org.opencastproject.usertracking.api.UserActionList; import org.opencastproject.usertracking.api.UserSession; import org.opencastproject.usertracking.api.UserTrackingException; import org.opencastproject.usertracking.api.UserTrackingService; import org.opencastproject.usertracking.endpoint.FootprintImpl; import org.opencastproject.usertracking.endpoint.FootprintsListImpl; import org.opencastproject.usertracking.endpoint.ReportImpl; import org.opencastproject.usertracking.endpoint.ReportItemImpl; import org.opencastproject.util.NotFoundException; import org.apache.commons.lang3.StringUtils; import org.osgi.service.cm.ConfigurationException; import org.osgi.service.cm.ManagedService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Collection; import java.util.Dictionary; import java.util.GregorianCalendar; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.NoResultException; import javax.persistence.Query; import javax.persistence.TemporalType; /** * Implementation of org.opencastproject.usertracking.api.UserTrackingService * * @see org.opencastproject.usertracking.api.UserTrackingService */ public class UserTrackingServiceImpl implements UserTrackingService, ManagedService { /** JPA persistence unit name */ public static final String PERSISTENCE_UNIT = "org.opencastproject.usertracking"; public static final String FOOTPRINT_KEY = "FOOTPRINT"; public static final String DETAILED_TRACKING = "org.opencastproject.usertracking.detailedtrack"; public static final String IP_LOGGING = "org.opencastproject.usertracking.log.ip"; public static final String USER_LOGGING = "org.opencastproject.usertracking.log.user"; public static final String SESSION_LOGGING = "org.opencastproject.usertracking.log.session"; private static final Logger logger = LoggerFactory.getLogger(UserTrackingServiceImpl.class); private boolean detailedTracking = false; private boolean logIp = true; private boolean logUser = true; private boolean logSession = true; /** The factory used to generate the entity manager */ protected EntityManagerFactory emf = null; /** OSGi DI */ /** OSGi DI */ void setEntityManagerFactory(EntityManagerFactory emf) { this.emf = emf; } /** * Activation callback to be executed once all dependencies are set */ public void activate() { logger.debug("activate()"); } @Override public void updated(Dictionary props) throws ConfigurationException { if (props == null) { logger.debug("Null properties in user tracking service, not doing detailed logging"); return; } Object val = props.get(DETAILED_TRACKING); if (val != null && String.class.isInstance(val)) { detailedTracking = Boolean.valueOf((String) val); } val = props.get(IP_LOGGING); if (val != null && String.class.isInstance(val)) { logIp = Boolean.valueOf((String) val); } val = props.get(USER_LOGGING); if (val != null && String.class.isInstance(val)) { logUser = Boolean.valueOf((String) val); } val = props.get(SESSION_LOGGING); if (val != null && String.class.isInstance(val)) { logSession = Boolean.valueOf((String) val); } } public int getViews(String mediapackageId) { EntityManager em = null; try { em = emf.createEntityManager(); Query q = em.createNamedQuery("countSessionsOfMediapackage"); q.setParameter("mediapackageId", mediapackageId); return ((Long) q.getSingleResult()).intValue(); } finally { if (em != null && em.isOpen()) { em.close(); } } } @SuppressWarnings("unchecked") public UserAction addUserFootprint(UserAction a, UserSession session) throws UserTrackingException { a.setType(FOOTPRINT_KEY); EntityManager em = null; EntityTransaction tx = null; if (!logIp) session.setUserIp("-omitted-"); if (!logUser) session.setUserId("-omitted-"); if (!logSession) session.setSessionId("-omitted-"); try { em = emf.createEntityManager(); tx = em.getTransaction(); tx.begin(); UserSession userSession = populateSession(em, session); Query q = em.createNamedQuery("findLastUserFootprintOfSession"); q.setMaxResults(1); q.setParameter("session", userSession); Collection<UserAction> userActions = q.getResultList(); if (userActions.size() >= 1) { UserAction last = userActions.iterator().next(); if (last.getMediapackageId().equals(a.getMediapackageId()) && last.getType().equals(a.getType()) && last.getOutpoint() == a.getInpoint()) { //We are assuming in this case that the sessions match and are unchanged (IP wise, for example) last.setOutpoint(a.getOutpoint()); a = last; a.setId(last.getId()); } else { a.setSession(userSession); em.persist(a); } } else { a.setSession(userSession); em.persist(a); } tx.commit(); return a; } catch (Exception e) { if (tx != null && tx.isActive()) { tx.rollback(); } throw new UserTrackingException(e); } finally { if (em != null && em.isOpen()) { em.close(); } } } public UserAction addUserTrackingEvent(UserAction a, UserSession session) throws UserTrackingException { EntityManager em = null; EntityTransaction tx = null; if (!logIp) session.setUserIp("-omitted-"); if (!logUser) session.setUserId("-omitted-"); if (!logSession) session.setSessionId("-omitted-"); try { em = emf.createEntityManager(); tx = em.getTransaction(); tx.begin(); UserSession userSession = populateSession(em, session); a.setSession(userSession); em.persist(a); tx.commit(); return a; } catch (Exception e) { if (tx.isActive()) { tx.rollback(); } throw new UserTrackingException(e); } finally { if (em != null && em.isOpen()) { em.close(); } } } private UserSession populateSession(EntityManager em, UserSession session) { //Try and find the session. If not found, persist it Query q = em.createNamedQuery("findUserSessionBySessionId"); q.setMaxResults(1); q.setParameter("sessionId", session.getSessionId()); UserSession userSession = null; try { userSession = (UserSession) q.getSingleResult(); } catch (NoResultException n) { userSession = session; em.persist(userSession); } return userSession; } @SuppressWarnings("unchecked") public UserActionList getUserActions(int offset, int limit) { UserActionList result = new UserActionListImpl(); result.setTotal(getTotal()); result.setOffset(offset); result.setLimit(limit); EntityManager em = null; try { em = emf.createEntityManager(); Query q = em.createNamedQuery("findUserActions"); q.setFirstResult(offset); if (limit > 0) q.setMaxResults(limit); Collection<UserAction> userActions = q.getResultList(); for (UserAction a : userActions) { result.add(a); } return result; } finally { if (em != null && em.isOpen()) { em.close(); } } } private int getTotal() { EntityManager em = null; try { em = emf.createEntityManager(); Query q = em.createNamedQuery("findTotal"); return ((Long) q.getSingleResult()).intValue(); } finally { if (em != null && em.isOpen()) { em.close(); } } } @SuppressWarnings("unchecked") public UserActionList getUserActionsByType(String type, int offset, int limit) { UserActionList result = new UserActionListImpl(); result.setTotal(getTotal(type)); result.setOffset(offset); result.setLimit(limit); EntityManager em = null; try { em = emf.createEntityManager(); Query q = em.createNamedQuery("findUserActionsByType"); q.setParameter("type", type); q.setFirstResult(offset); if (limit > 0) q.setMaxResults(limit); Collection<UserAction> userActions = q.getResultList(); for (UserAction a : userActions) { result.add(a); } return result; } finally { if (em != null && em.isOpen()) { em.close(); } } } private int getTotal(String type) { EntityManager em = null; try { em = emf.createEntityManager(); Query q = em.createNamedQuery("findTotalByType"); q.setParameter("type", type); return ((Long) q.getSingleResult()).intValue(); } finally { if (em != null && em.isOpen()) { em.close(); } } } public UserActionList getUserActionsByTypeAndMediapackageId(String type, String mediapackageId, int offset, int limit) { UserActionList result = new UserActionListImpl(); result.setTotal(getTotal(type, mediapackageId)); result.setOffset(offset); result.setLimit(limit); EntityManager em = null; try { em = emf.createEntityManager(); Query q = em.createNamedQuery("findUserActionsByTypeAndMediapackageId"); q.setParameter("type", type); q.setParameter("mediapackageId", mediapackageId); q.setFirstResult(offset); if (limit > 0) q.setMaxResults(limit); @SuppressWarnings("unchecked") Collection<UserAction> userActions = q.getResultList(); for (UserAction a : userActions) { result.add(a); } return result; } finally { if (em != null && em.isOpen()) { em.close(); } } } @SuppressWarnings("unchecked") public UserActionList getUserActionsByTypeAndDay(String type, String day, int offset, int limit) { UserActionList result = new UserActionListImpl(); int year = Integer.parseInt(day.substring(0, 4)); int month = Integer.parseInt(day.substring(4, 6)) - 1; int date = Integer.parseInt(day.substring(6, 8)); Calendar calBegin = new GregorianCalendar(); calBegin.set(year, month, date, 0, 0); Calendar calEnd = new GregorianCalendar(); calEnd.set(year, month, date, 23, 59); result.setTotal(getTotal(type, calBegin, calEnd)); result.setOffset(offset); result.setLimit(limit); EntityManager em = null; try { em = emf.createEntityManager(); Query q = em.createNamedQuery("findUserActionsByTypeAndIntervall"); q.setParameter("type", type); q.setParameter("begin", calBegin, TemporalType.TIMESTAMP); q.setParameter("end", calEnd, TemporalType.TIMESTAMP); q.setFirstResult(offset); if (limit > 0) q.setMaxResults(limit); Collection<UserAction> userActions = q.getResultList(); for (UserAction a : userActions) { result.add(a); } return result; } finally { if (em != null && em.isOpen()) { em.close(); } } } public UserActionList getUserActionsByTypeAndMediapackageIdByDate(String type, String mediapackageId, int offset, int limit) { UserActionList result = new UserActionListImpl(); result.setTotal(getTotal(type, mediapackageId)); result.setOffset(offset); result.setLimit(limit); EntityManager em = null; try { em = emf.createEntityManager(); Query q = em.createNamedQuery("findUserActionsByMediaPackageAndTypeAscendingByDate"); q.setParameter("type", type); q.setParameter("mediapackageId", mediapackageId); q.setFirstResult(offset); if (limit > 0) q.setMaxResults(limit); @SuppressWarnings("unchecked") Collection<UserAction> userActions = q.getResultList(); for (UserAction a : userActions) { result.add(a); } return result; } finally { if (em != null && em.isOpen()) { em.close(); } } } public UserActionList getUserActionsByTypeAndMediapackageIdByDescendingDate(String type, String mediapackageId, int offset, int limit) { UserActionList result = new UserActionListImpl(); result.setTotal(getTotal(type, mediapackageId)); result.setOffset(offset); result.setLimit(limit); EntityManager em = null; try { em = emf.createEntityManager(); Query q = em.createNamedQuery("findUserActionsByMediaPackageAndTypeDescendingByDate"); q.setParameter("type", type); q.setParameter("mediapackageId", mediapackageId); q.setFirstResult(offset); if (limit > 0) q.setMaxResults(limit); @SuppressWarnings("unchecked") Collection<UserAction> userActions = q.getResultList(); for (UserAction a : userActions) { result.add(a); } return result; } finally { if (em != null && em.isOpen()) { em.close(); } } } private int getTotal(String type, Calendar calBegin, Calendar calEnd) { EntityManager em = null; try { em = emf.createEntityManager(); Query q = em.createNamedQuery("findTotalByTypeAndIntervall"); q.setParameter("type", type); q.setParameter("begin", calBegin, TemporalType.TIMESTAMP); q.setParameter("end", calEnd, TemporalType.TIMESTAMP); return ((Long) q.getSingleResult()).intValue(); } finally { if (em != null && em.isOpen()) { em.close(); } } } private int getTotal(String type, String mediapackageId) { EntityManager em = null; try { em = emf.createEntityManager(); Query q = em.createNamedQuery("findTotalByTypeAndMediapackageId"); q.setParameter("type", type); q.setParameter("mediapackageId", mediapackageId); return ((Long) q.getSingleResult()).intValue(); } finally { if (em != null && em.isOpen()) { em.close(); } } } @SuppressWarnings("unchecked") public UserActionList getUserActionsByDay(String day, int offset, int limit) { UserActionList result = new UserActionListImpl(); int year = Integer.parseInt(day.substring(0, 4)); int month = Integer.parseInt(day.substring(4, 6)) - 1; int date = Integer.parseInt(day.substring(6, 8)); Calendar calBegin = new GregorianCalendar(); calBegin.set(year, month, date, 0, 0); Calendar calEnd = new GregorianCalendar(); calEnd.set(year, month, date, 23, 59); result.setTotal(getTotal(calBegin, calEnd)); result.setOffset(offset); result.setLimit(limit); EntityManager em = null; try { em = emf.createEntityManager(); Query q = em.createNamedQuery("findUserActionsByIntervall"); q.setParameter("begin", calBegin, TemporalType.TIMESTAMP); q.setParameter("end", calEnd, TemporalType.TIMESTAMP); q.setFirstResult(offset); if (limit > 0) q.setMaxResults(limit); Collection<UserAction> userActions = q.getResultList(); for (UserAction a : userActions) { result.add(a); } return result; } finally { if (em != null && em.isOpen()) { em.close(); } } } private int getTotal(Calendar calBegin, Calendar calEnd) { EntityManager em = null; try { em = emf.createEntityManager(); Query q = em.createNamedQuery("findTotalByIntervall"); q.setParameter("begin", calBegin, TemporalType.TIMESTAMP); q.setParameter("end", calEnd, TemporalType.TIMESTAMP); return ((Long) q.getSingleResult()).intValue(); } finally { if (em != null && em.isOpen()) { em.close(); } } } public Report getReport(int offset, int limit) { Report report = new ReportImpl(); report.setLimit(limit); report.setOffset(offset); EntityManager em = null; try { em = emf.createEntityManager(); Query q = em.createNamedQuery("countSessionsGroupByMediapackage"); q.setFirstResult(offset); if (limit > 0) q.setMaxResults(limit); @SuppressWarnings("unchecked") List<Object[]> result = q.getResultList(); ReportItem item; for (Object[] a : result) { item = new ReportItemImpl(); item.setEpisodeId((String) a[0]); item.setViews((Long) a[1]); item.setPlayed((Long) a[2]); report.add(item); } return report; } finally { if (em != null && em.isOpen()) { em.close(); } } } public Report getReport(String from, String to, int offset, int limit) throws ParseException { Report report = new ReportImpl(); report.setLimit(limit); report.setOffset(offset); Calendar calBegin = new GregorianCalendar(); Calendar calEnd = new GregorianCalendar(); SimpleDateFormat complex = new SimpleDateFormat("yyyyMMddhhmm"); SimpleDateFormat simple = new SimpleDateFormat("yyyyMMdd"); //Try to parse the from calendar try { calBegin.setTime(complex.parse(from)); } catch (ParseException e) { calBegin.setTime(simple.parse(from)); } //Try to parse the to calendar try { calEnd.setTime(complex.parse(to)); } catch (ParseException e) { calEnd.setTime(simple.parse(to)); } EntityManager em = null; try { em = emf.createEntityManager(); Query q = em.createNamedQuery("countSessionsGroupByMediapackageByIntervall"); q.setFirstResult(offset); if (limit > 0) q.setMaxResults(limit); q.setParameter("begin", calBegin, TemporalType.TIMESTAMP); q.setParameter("end", calEnd, TemporalType.TIMESTAMP); @SuppressWarnings("unchecked") List<Object[]> result = q.getResultList(); ReportItem item; for (Object[] a : result) { item = new ReportItemImpl(); item.setEpisodeId((String) a[0]); item.setViews((Long) a[1]); item.setPlayed((Long) a[2]); report.add(item); } return report; } finally { if (em != null && em.isOpen()) { em.close(); } } } public FootprintList getFootprints(String mediapackageId, String userId) { EntityManager em = null; if (! logUser) userId = null; try { em = emf.createEntityManager(); Query q = null; if (StringUtils.trimToNull(userId) == null) { q = em.createNamedQuery("findUserActionsByTypeAndMediapackageIdOrderByOutpointDESC"); } else { q = em.createNamedQuery("findUserActionsByTypeAndMediapackageIdByUserOrderByOutpointDESC"); q.setParameter("userid", userId); } q.setParameter("type", FOOTPRINT_KEY); q.setParameter("mediapackageId", mediapackageId); @SuppressWarnings("unchecked") Collection<UserAction> userActions = q.getResultList(); int[] resultArray = new int[1]; boolean first = true; for (UserAction a : userActions) { if (first) { // Get one more item than the known outpoint to append a footprint of 0 views at the end of the result set resultArray = new int[a.getOutpoint() + 1]; first = false; } for (int i = a.getInpoint(); i < a.getOutpoint(); i++) { resultArray[i]++; } } FootprintList list = new FootprintsListImpl(); int current = -1; int last = -1; for (int i = 0; i < resultArray.length; i++) { current = resultArray[i]; if (last != current) { Footprint footprint = new FootprintImpl(); footprint.setPosition(i); footprint.setViews(current); list.add(footprint); } last = current; } return list; } finally { if (em != null && em.isOpen()) { em.close(); } } } /** * {@inheritDoc} * * @see org.opencastproject.usertracking.api.UserTrackingService#getUserAction(java.lang.Long) */ @Override public UserAction getUserAction(Long id) throws UserTrackingException, NotFoundException { EntityManager em = null; UserActionImpl result = null; try { em = emf.createEntityManager(); result = em.find(UserActionImpl.class, id); } catch (Exception e) { throw new UserTrackingException(e); } finally { if (em != null && em.isOpen()) { em.close(); } } if (result == null) { throw new NotFoundException("No UserAction found with id='" + id + "'"); } else { return result; } } /** * {@inheritDoc} * * @see org.opencastproject.usertracking.api.UserTrackingService#getUserTrackingEnabled() */ @Override public boolean getUserTrackingEnabled() { return detailedTracking; } }