/**
* Licensed to Apereo under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Apereo licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a
* copy of the License at the following location:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jasig.portlet.blackboardvcportlet.dao.impl;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.regex.Pattern;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.jasig.jpa.BaseJpaDao;
import org.jasig.jpa.OpenEntityManager;
import org.jasig.portlet.blackboardvcportlet.data.AccessType;
import org.jasig.portlet.blackboardvcportlet.data.ConferenceUser;
import org.jasig.portlet.blackboardvcportlet.data.Multimedia;
import org.jasig.portlet.blackboardvcportlet.data.Presentation;
import org.jasig.portlet.blackboardvcportlet.data.RecordingMode;
import org.jasig.portlet.blackboardvcportlet.data.Session;
import org.jasig.portlet.blackboardvcportlet.data.SessionRecording;
import org.jasig.portlet.blackboardvcportlet.data.SessionTelephony;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import com.elluminate.sas.BlackboardSessionResponse;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableSet;
@Repository
public class SessionDaoImpl extends BaseJpaDao implements InternalSessionDao {
private static final Pattern USER_LIST_DELIM = Pattern.compile(",");
private CriteriaQuery<SessionImpl> findAllSessions;
private InternalConferenceUserDao conferenceUserDao;
private InternalPresentationDao presentationDao;
private InternalMultimediaDao multimediaDao;
@Autowired
public void setPresentationDao(InternalPresentationDao dao) {
this.presentationDao = dao;
}
@Autowired
public void setMultimediaDao(InternalMultimediaDao dao) {
this.multimediaDao = dao;
}
@Autowired
public void setConferenceUserDao(InternalConferenceUserDao conferenceUserDao) {
this.conferenceUserDao = conferenceUserDao;
}
@Override
public void afterPropertiesSet() throws Exception {
this.findAllSessions = this.createCriteriaQuery(new Function<CriteriaBuilder, CriteriaQuery<SessionImpl>>() {
@Override
public CriteriaQuery<SessionImpl> apply(CriteriaBuilder cb) {
final CriteriaQuery<SessionImpl> criteriaQuery = cb.createQuery(SessionImpl.class);
final Root<SessionImpl> definitionRoot = criteriaQuery.from(SessionImpl.class);
criteriaQuery.select(definitionRoot);
return criteriaQuery;
}
});
}
@Override
public Set<Session> getAllSessions() {
final TypedQuery<SessionImpl> query = this.createQuery(this.findAllSessions);
return new LinkedHashSet<Session>(query.getResultList());
}
@Override
public Set<ConferenceUser> getSessionChairs(Session session) {
if (session == null) {
return null;
}
final SessionImpl sessionImpl = this.getSession(session.getSessionId());
if (sessionImpl == null) {
return null;
}
//Create a copy to trigger loading of the user data
return ImmutableSet.<ConferenceUser>copyOf(sessionImpl.getChairs());
}
@Override
public Set<Multimedia> getSessionMultimedias(Session session) {
if (session == null) {
return null;
}
final SessionImpl sessionImpl = this.getSession(session.getSessionId());
if (sessionImpl == null) {
return null;
}
//Create a copy to trigger loading of the user data
return ImmutableSet.<Multimedia>copyOf(sessionImpl.getMultimedias());
}
@Override
public Set<ConferenceUser> getSessionNonChairs(Session session) {
if (session == null) {
return null;
}
final SessionImpl sessionImpl = this.getSession(session.getSessionId());
if (sessionImpl == null) {
return null;
}
//Create a copy to trigger loading of the user data
return ImmutableSet.<ConferenceUser>copyOf(sessionImpl.getNonChairs());
}
@Override
public Set<SessionRecording> getSessionRecordings(Session session) {
if (session == null) {
return null;
}
final SessionImpl sessionImpl = this.getSession(session.getSessionId());
if (sessionImpl == null) {
return null;
}
//Create a copy to trigger loading of the user data
return ImmutableSet.<SessionRecording>copyOf(sessionImpl.getSessionRecordings());
}
@Override
public Set<SessionTelephony> getSessionTelephony(Session session) {
if (session == null) {
return null;
}
final SessionImpl sessionImpl = this.getSession(session.getSessionId());
if (sessionImpl == null) {
return null;
}
//Create a copy to trigger loading of the user data
return ImmutableSet.<SessionTelephony>copyOf(sessionImpl.getSessionTelephony());
}
@Override
public SessionImpl getSession(long sessionId) {
final EntityManager entityManager = this.getEntityManager();
return entityManager.find(SessionImpl.class, sessionId);
}
@Override
@OpenEntityManager
public SessionImpl getSessionByBlackboardId(long bbSessionId) {
final NaturalIdQuery<SessionImpl> query = this.createNaturalIdQuery(SessionImpl.class);
query.using(SessionImpl_.bbSessionId, bbSessionId);
return query.load();
}
@Override
@Transactional
public SessionImpl createSession(BlackboardSessionResponse sessionResponse, String guestUrl) {
//Find the creator user
final String creatorId = sessionResponse.getCreatorId();
ConferenceUserImpl creator = this.conferenceUserDao.getUserByUniqueId(creatorId);
if (creator == null) {
//This should be pretty rare as the creator should be the currently authd user
logger.warn("Internal user for session creator {} doesn't exist for session {}. Creating a bare bones user to compensate", creatorId, sessionResponse.getSessionId());
creator = this.conferenceUserDao.createInternalUser(creatorId);
}
//Create and populate a new blackboardSession
final SessionImpl session = new SessionImpl(sessionResponse.getSessionId(), creator);
updateBlackboardSession(sessionResponse, session);
session.setGuestUrl(guestUrl);
//Persist and return the new session
final EntityManager entityManager = this.getEntityManager();
entityManager.persist(session);
creator.getOwnedSessions().add(session);
entityManager.persist(creator);
return session;
}
@Override
@Transactional
public SessionImpl updateSession(BlackboardSessionResponse sessionResponse) {
//Find the existing blackboardSession
final SessionImpl session = this.getSessionByBlackboardId(sessionResponse.getSessionId());
if (session == null) {
//TODO should this automatically fall back to create?
throw new IncorrectResultSizeDataAccessException("No BlackboardSession could be found for sessionId " + sessionResponse.getSessionId(), 1);
}
//Copy over the response data
updateBlackboardSession(sessionResponse, session);
this.getEntityManager().persist(session);
return session;
}
@Override
@Transactional
public Session addPresentationToSession(Session session, Presentation presentation) {
SessionImpl blackboardSession = this.getSession(session.getSessionId());
if(blackboardSession == null) {
throw new IncorrectResultSizeDataAccessException("No BlackboardSession could be found for sessionId " + session.getSessionId(), 1);
}
PresentationImpl bbPresentation = presentationDao.getPresentationById(presentation.getPresentationId());
if(bbPresentation == null) {
throw new IncorrectResultSizeDataAccessException("No presentation could be found for blackboard presentationId " + presentation.getBbPresentationId(), 1);
}
blackboardSession.setPresentation(bbPresentation);
this.getEntityManager().persist(blackboardSession);
return blackboardSession;
}
@Override
@Transactional
public Session removePresentationFromSession(Session session) {
SessionImpl blackboardSession = this.getSession(session.getSessionId());
if(blackboardSession == null) {
throw new IncorrectResultSizeDataAccessException("No BlackboardSession could be found for sessionId " + session.getSessionId(), 1);
}
blackboardSession.setPresentation(null);
this.getEntityManager().persist(blackboardSession);
return blackboardSession;
}
@Override
@Transactional
public Session addMultimediaToSession(Session session, Multimedia multimedia) {
SessionImpl blackboardSession = this.getSession(session.getSessionId());
if(blackboardSession == null) {
throw new IncorrectResultSizeDataAccessException("No BlackboardSession could be found for sessionId " + session.getSessionId(), 1);
}
MultimediaImpl mm = multimediaDao.getMultimediaById(multimedia.getMultimediaId());
if(mm == null) {
throw new IncorrectResultSizeDataAccessException("No multimedia could be found for blackboard multimediaId " + multimedia.getBbMultimediaId(), 1);
}
blackboardSession.getMultimedias().add(mm);
this.getEntityManager().persist(blackboardSession);
return blackboardSession;
}
@Override
@Transactional
public Session deleteMultimediaFromSession(Session session, Multimedia multimedia) {
SessionImpl blackboardSession = this.getSession(session.getSessionId());
if(blackboardSession == null) {
throw new IncorrectResultSizeDataAccessException("No BlackboardSession could be found for sessionId " + session.getSessionId(), 1);
}
Multimedia mm = multimediaDao.getMultimediaById(multimedia.getMultimediaId());
if(mm == null) {
throw new IncorrectResultSizeDataAccessException("No multimedia could be found for blackboard multimediaId " + multimedia.getBbMultimediaId(), 1);
}
blackboardSession.getMultimedias().remove(multimedia);
this.getEntityManager().persist(blackboardSession);
return blackboardSession;
}
@Override
@Transactional
public void deleteSession(Session session) {
Validate.notNull(session, "session can not be null");
final SessionImpl sessionImpl = this.getSession(session.getSessionId());
final EntityManager entityManager = this.getEntityManager();
final ConferenceUserImpl creator = sessionImpl.getCreator();
creator.getOwnedSessions().remove(sessionImpl);
entityManager.persist(creator);
for (final ConferenceUserImpl user : sessionImpl.getChairs()) {
user.getChairedSessions().remove(sessionImpl);
entityManager.persist(user);
}
for (final ConferenceUserImpl user : sessionImpl.getNonChairs()) {
user.getNonChairedSessions().remove(sessionImpl);
entityManager.persist(user);
}
entityManager.remove(sessionImpl);
}
@Override
@Transactional
public void clearSessionUserList(long sessionId, boolean isChairList) {
final SessionImpl sessionImpl = this.getSession(sessionId);
final EntityManager entityManager = this.getEntityManager();
if(isChairList) {//clear chair list
for (final ConferenceUserImpl user : sessionImpl.getChairs()) {
user.getChairedSessions().remove(sessionImpl);
entityManager.persist(user);
}
sessionImpl.getChairs().clear();
entityManager.persist(sessionImpl);
} else { //clear non chair list
for (final ConferenceUserImpl user : sessionImpl.getNonChairs()) {
user.getNonChairedSessions().remove(sessionImpl);
entityManager.persist(user);
}
sessionImpl.getNonChairs().clear();
entityManager.persist(sessionImpl);
}
}
/**
* Sync all data from the {@link BlackboardSessionResponse} to the {@link SessionImpl}
*/
private void updateBlackboardSession(BlackboardSessionResponse sessionResponse, SessionImpl session) {
session.setAccessType(AccessType.resolveAccessType(sessionResponse.getAccessType()));
session.setAllowInSessionInvites(sessionResponse.isAllowInSessionInvites());
session.setBoundaryTime(sessionResponse.getBoundaryTime());
session.setChairNotes(sessionResponse.getChairNotes());
session.setEndTime(DaoUtils.toDateTime(sessionResponse.getEndTime()));
session.setHideParticipantNames(sessionResponse.isHideParticipantNames());
session.setMaxCameras(sessionResponse.getMaxCameras());
session.setMaxTalkers(sessionResponse.getMaxTalkers());
session.setMustBeSupervised(sessionResponse.isMustBeSupervised());
session.setNonChairNotes(sessionResponse.getNonChairNotes());
session.setOpenChair(sessionResponse.isOpenChair());
session.setRaiseHandOnEnter(sessionResponse.isRaiseHandOnEnter());
session.setRecordingMode(RecordingMode.resolveRecordingMode(sessionResponse.getRecordingModeType()));
session.setRecordings(sessionResponse.isRecordings());
session.setReserveSeats(sessionResponse.getReserveSeats());
session.setSessionName(sessionResponse.getSessionName());
session.setStartTime(DaoUtils.toDateTime(sessionResponse.getStartTime()));
session.setVersionId(sessionResponse.getVersionId());
session.setPermissionsOn(sessionResponse.isPermissionsOn());
session.setSecureSignOn(sessionResponse.isSecureSignOn());
updateUserList(sessionResponse, session, UserListType.CHAIR);
updateUserList(sessionResponse, session, UserListType.NON_CHAIR);
}
/**
* Syncs the user list (chair or non-chair) from the {@link BlackboardSessionResponse} to the {@link SessionImpl}. Handles
* creating/updating the associated {@link ConferenceUser} objects
*
* @param sessionResponse Source of user list data
* @param blackboardSession Destination to be updated with user list data
* @param type The type of user list data to sync
*/
private void updateUserList(BlackboardSessionResponse sessionResponse, SessionImpl blackboardSession, UserListType type) {
final String userList = type.getUserList(sessionResponse);
final String[] userIds = USER_LIST_DELIM.split(userList);
final Set<ConferenceUserImpl> existingUsers = type.getUserSet(blackboardSession);
final Set<ConferenceUser> newUsers = new HashSet<ConferenceUser>(userIds.length);
for (String bbUserId : userIds) {
bbUserId = StringUtils.trimToNull(bbUserId);
//Skip empty/null userIds
if (bbUserId == null) {
continue;
}
//Get/Create the participating user
ConferenceUserImpl user;
if (bbUserId.startsWith(ConferenceUser.EXTERNAL_USERID_PREFIX)) {
final String externalUserEmail = bbUserId.substring(ConferenceUser.EXTERNAL_USERID_PREFIX.length());
user = this.conferenceUserDao.getExternalUserByEmail(externalUserEmail);
if (user == null) {
user = this.conferenceUserDao.createExternalUser(externalUserEmail);
}
}
else {
user = this.conferenceUserDao.getUserByUniqueId(bbUserId);
if (user == null) {
logger.warn("Internal user for {} creator {} doesn't exist for session {}. Creating a bare bones user to compensate", bbUserId, type, sessionResponse.getSessionId());
user = this.conferenceUserDao.createInternalUser(bbUserId);
}
}
//Update the user's set of chaired sessions
final boolean added = type.associateSession(user, blackboardSession);
//User was modified, make sure we tell hibernate to persist them
if (added) {
this.conferenceUserDao.updateUser(user);
}
//Add the user to the new set and make sure the user is in the existing set
newUsers.add(user);
existingUsers.add(user);
}
//Use this approach to remove any users that are no longer in the list. Mutating the existing
//collection is slightly more expensive in CPU time but significantly less expensive for the
//hibernate layer to persist
for (final Iterator<ConferenceUserImpl> existingUserItr = existingUsers.iterator(); existingUserItr.hasNext();) {
final ConferenceUser existingUser = existingUserItr.next();
//Check each existing user to see if they should no longer be a chair
if (!newUsers.contains(existingUser)) {
//Remove from existing chairs set
existingUserItr.remove();
//Update the user's associate with the session
final ConferenceUserImpl user = this.conferenceUserDao.getUser(existingUser.getUserId());
final boolean removed = type.unassociateSession(user, blackboardSession);
if (removed) {
this.conferenceUserDao.updateUser(user);
}
}
}
}
/**
* Utility enum to hide the specific methods involved with updating chair vs
* non-chair user to session associations.
*/
private enum UserListType {
CHAIR {
@Override
public String getUserList(BlackboardSessionResponse sessionResponse) {
return sessionResponse.getChairList();
}
@Override
public Set<ConferenceUserImpl> getUserSet(SessionImpl blackboardSession) {
return blackboardSession.getChairs();
}
@Override
public boolean associateSession(ConferenceUserImpl user, SessionImpl session) {
session.getChairs().add(user);
return user.getChairedSessions().add(session);
}
@Override
public boolean unassociateSession(ConferenceUserImpl user, SessionImpl session) {
session.getChairs().remove(user);
return user.getChairedSessions().remove(session);
}
},
NON_CHAIR{
@Override
public String getUserList(BlackboardSessionResponse sessionResponse) {
return sessionResponse.getNonChairList();
}
@Override
public Set<ConferenceUserImpl> getUserSet(SessionImpl blackboardSession) {
return blackboardSession.getNonChairs();
}
@Override
public boolean associateSession(ConferenceUserImpl user, SessionImpl session) {
session.getNonChairs().add(user);
return user.getNonChairedSessions().add(session);
}
@Override
public boolean unassociateSession(ConferenceUserImpl user, SessionImpl session) {
session.getNonChairs().remove(user);
return user.getNonChairedSessions().remove(session);
}
};
abstract String getUserList(BlackboardSessionResponse sessionResponse);
abstract Set<ConferenceUserImpl> getUserSet(SessionImpl blackboardSession);
abstract boolean associateSession(ConferenceUserImpl user, SessionImpl session);
abstract boolean unassociateSession(ConferenceUserImpl user, SessionImpl session);
}
}