/**
* 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:
*
* <p>http://www.apache.org/licenses/LICENSE-2.0
*
* <p>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.apereo.portal.portlet.dao.jpa;
import com.google.common.base.Function;
import java.security.SecureRandom;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.ParameterExpression;
import javax.persistence.criteria.Root;
import javax.servlet.http.Cookie;
import org.apache.commons.codec.binary.Base64;
import org.apereo.portal.jpa.BasePortalJpaDao;
import org.apereo.portal.jpa.OpenEntityManager;
import org.apereo.portal.portlet.dao.IPortletCookieDao;
import org.apereo.portal.portlet.om.IPortalCookie;
import org.apereo.portal.portlet.om.IPortletCookie;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Repository;
/**
* JPA implementation of {@link IPortletCookieDao}.
*
*/
@Repository("portletCookieDao")
@Qualifier("persistence")
public class JpaPortletCookieDaoImpl extends BasePortalJpaDao implements IPortletCookieDao {
private final SecureRandom secureRandom = new SecureRandom();
private String deletePortalCookieQueryString;
private String deleteEmptyPortalCookieQueryString;
private String deletePortletCookieQueryString;
private CriteriaQuery<PortletCookieImpl> findExpiredByParentPortletCookiesQuery;
private ParameterExpression<DateTime> nowParameter;
protected static final int DEFAULT_EMPTY_MAX_AGE = (int) TimeUnit.DAYS.toSeconds(1);
private int emptyCookieMaxAge = DEFAULT_EMPTY_MAX_AGE;
@Override
public void afterPropertiesSet() throws Exception {
this.nowParameter = this.createParameterExpression(DateTime.class, "now");
this.deletePortalCookieQueryString =
"DELETE FROM "
+ PortalCookieImpl.class.getName()
+ " e "
+ "WHERE e."
+ PortalCookieImpl_.expires.getName()
+ " <= :"
+ this.nowParameter.getName();
this.deleteEmptyPortalCookieQueryString =
"DELETE FROM "
+ PortalCookieImpl.class.getName()
+ " e "
+ "WHERE e."
+ PortalCookieImpl_.expires.getName()
+ " <= :"
+ this.nowParameter.getName()
+ " AND "
+ "e."
+ PortalCookieImpl_.portletCookies.getName()
+ " IS EMPTY";
this.deletePortletCookieQueryString =
"DELETE FROM "
+ PortletCookieImpl.class.getName()
+ " e "
+ "WHERE e."
+ PortletCookieImpl_.expires.getName()
+ " <= :"
+ this.nowParameter.getName();
this.findExpiredByParentPortletCookiesQuery =
this.createCriteriaQuery(
new Function<CriteriaBuilder, CriteriaQuery<PortletCookieImpl>>() {
@Override
public CriteriaQuery<PortletCookieImpl> apply(CriteriaBuilder cb) {
final CriteriaQuery<PortletCookieImpl> criteriaQuery =
cb.createQuery(PortletCookieImpl.class);
final Root<PortletCookieImpl> typeRoot =
criteriaQuery.from(PortletCookieImpl.class);
criteriaQuery.select(typeRoot);
criteriaQuery.where(
cb.lessThanOrEqualTo(
typeRoot.get(PortletCookieImpl_.portalCookie)
.get(PortalCookieImpl_.expires),
nowParameter));
return criteriaQuery;
}
});
}
/**
* Generates a 40 character unique value.
*
* @return
*/
private String generateNewCookieId() {
final byte[] keyBytes = new byte[30];
this.secureRandom.nextBytes(keyBytes);
return Base64.encodeBase64URLSafeString(keyBytes);
}
@Override
@PortalTransactional
public IPortalCookie createPortalCookie(int maxAge) {
//Make sure our unique ID doesn't already exist by really small random chance
String uniqueId;
do {
uniqueId = generateNewCookieId();
} while (this.getPortalCookie(uniqueId) != null);
//Calculate the expiration date for the cookie
final DateTime expiration = DateTime.now().plusSeconds(maxAge);
//Create and persist
final IPortalCookie portalCookie = new PortalCookieImpl(uniqueId, expiration);
this.getEntityManager().persist(portalCookie);
return portalCookie;
}
@OpenEntityManager(unitName = PERSISTENCE_UNIT_NAME)
@Override
public IPortalCookie getPortalCookie(String portalCookieValue) {
final NaturalIdQuery<PortalCookieImpl> query =
this.createNaturalIdQuery(PortalCookieImpl.class);
query.using(PortalCookieImpl_.value, portalCookieValue);
return query.load();
}
@Override
@PortalTransactional
public IPortalCookie updatePortalCookieExpiration(IPortalCookie portalCookie, int maxAge) {
//Calculate expiration date and update the portal cookie
final DateTime expiration = DateTime.now().plusSeconds(maxAge);
portalCookie.setExpires(expiration);
this.getEntityManager().persist(portalCookie);
return portalCookie;
}
@Override
@PortalTransactional
public void purgeExpiredCookies(int maxAge) {
final DateTime now = DateTime.now();
logger.debug("begin portlet cookie expiration");
final EntityManager entityManager = this.getEntityManager();
final Query deletePortletCookieQuery =
entityManager.createQuery(this.deletePortletCookieQueryString);
deletePortletCookieQuery.setParameter(this.nowParameter.getName(), now);
final int deletedPortletCookies = deletePortletCookieQuery.executeUpdate();
logger.debug(
"finished purging {} portlet cookies with expiration before {}",
deletedPortletCookies,
now);
final TypedQuery<PortletCookieImpl> expiredByParentCookiesQuery =
this.createQuery(findExpiredByParentPortletCookiesQuery);
expiredByParentCookiesQuery.setParameter(this.nowParameter.getName(), now);
final List<PortletCookieImpl> indirectlyExpiredCookies =
expiredByParentCookiesQuery.getResultList();
for (final PortletCookieImpl portletCookieImpl : indirectlyExpiredCookies) {
entityManager.remove(portletCookieImpl);
}
logger.debug(
"finished purging {} portlet cookies with parent expiration before {}",
indirectlyExpiredCookies.size(),
now);
logger.debug("begin portal cookie expiration");
final Query deletePortalCookieQuery =
entityManager.createQuery(this.deletePortalCookieQueryString);
deletePortalCookieQuery.setParameter(this.nowParameter.getName(), now);
final int deletedPortalCookies = deletePortalCookieQuery.executeUpdate();
logger.debug(
"finished purging {} portal cookies with expiration before {}",
deletedPortalCookies,
now);
final Query deleteEmptyPortalCookieQuery =
entityManager.createQuery(this.deleteEmptyPortalCookieQueryString);
//Add the maxAge to now and then subtract the emptyCookieMaxAge
//For example (now + 1 year) - 1 day == the empty-cookie expiration date
final DateTime emptyExpiration = now.plusSeconds(maxAge).minusSeconds(emptyCookieMaxAge);
deleteEmptyPortalCookieQuery.setParameter(this.nowParameter.getName(), emptyExpiration);
final int deletedEmptyPortalCookies = deleteEmptyPortalCookieQuery.executeUpdate();
logger.debug(
"finished purging {} empty portal cookies with expiration before {}",
deletedEmptyPortalCookies,
emptyExpiration);
}
/*
* (non-Javadoc)
* @see org.apereo.portal.portlet.dao.IPortletCookieDao#updatePortletCookie(org.apereo.portal.portlet.om.IPortalCookie, javax.servlet.http.Cookie)
*/
@Override
@PortalTransactional
public IPortalCookie addOrUpdatePortletCookie(IPortalCookie portalCookie, Cookie cookie) {
final Set<IPortletCookie> portletCookies = portalCookie.getPortletCookies();
boolean found = false;
final String name = cookie.getName();
final EntityManager entityManager = this.getEntityManager();
for (final Iterator<IPortletCookie> portletCookieItr = portletCookies.iterator();
portletCookieItr.hasNext();
) {
final IPortletCookie portletCookie = portletCookieItr.next();
if (name.equals(portletCookie.getName())) {
//Delete cookies with a maxAge of 0
if (cookie.getMaxAge() == 0) {
portletCookieItr.remove();
entityManager.remove(portletCookie);
} else {
portletCookie.updateFromCookie(cookie);
}
found = true;
break;
}
}
if (!found) {
IPortletCookie newPortletCookie = new PortletCookieImpl(portalCookie, cookie);
portletCookies.add(newPortletCookie);
}
entityManager.persist(portalCookie);
return portalCookie;
}
}