diff --git a/ccm-bundle-devel-wildfly-web/src/main/resources/log4j2.xml b/ccm-bundle-devel-wildfly-web/src/main/resources/log4j2.xml index b7ab76c41..3f6bc94a3 100644 --- a/ccm-bundle-devel-wildfly-web/src/main/resources/log4j2.xml +++ b/ccm-bundle-devel-wildfly-web/src/main/resources/log4j2.xml @@ -9,6 +9,12 @@ + + + + @@ -19,6 +25,9 @@ level="debug"> + + diff --git a/ccm-bundle-devel-wildfly-web/src/main/webapp/WEB-INF/web.xml b/ccm-bundle-devel-wildfly-web/src/main/webapp/WEB-INF/web.xml index ac7f9ed0c..4390f3b4e 100644 --- a/ccm-bundle-devel-wildfly-web/src/main/webapp/WEB-INF/web.xml +++ b/ccm-bundle-devel-wildfly-web/src/main/webapp/WEB-INF/web.xml @@ -7,6 +7,12 @@ LibreCCM Devel Bundle for Wildfly + + + COOKIE + + + ShiroFilter org.apache.shiro.web.servlet.ShiroFilter @@ -15,10 +21,10 @@ ShiroFilter /* - REQUEST + diff --git a/ccm-core/src/main/java/com/arsdigita/bebop/BebopConfig.java b/ccm-core/src/main/java/com/arsdigita/bebop/BebopConfig.java index 3ad67cdff..4e3d6fbf7 100755 --- a/ccm-core/src/main/java/com/arsdigita/bebop/BebopConfig.java +++ b/ccm-core/src/main/java/com/arsdigita/bebop/BebopConfig.java @@ -28,7 +28,6 @@ import java.util.Arrays; import java.util.HashSet; import java.util.Objects; import java.util.Set; -import java.util.StringJoiner; import org.libreccm.cdi.utils.CdiUtil; import org.libreccm.configuration.Configuration; @@ -83,9 +82,8 @@ public final class BebopConfig { private Boolean showClassName = false; public static BebopConfig getConfig() { - final CdiUtil cdiUtil = new CdiUtil(); - final ConfigurationManager confManager = cdiUtil.findBean( - ConfigurationManager.class); + final ConfigurationManager confManager = CdiUtil.createCdiUtil() + .findBean(ConfigurationManager.class); return confManager.findConfiguration(BebopConfig.class); } diff --git a/ccm-core/src/main/java/com/arsdigita/bebop/page/BebopApplicationServlet.java b/ccm-core/src/main/java/com/arsdigita/bebop/page/BebopApplicationServlet.java index 12e248c1d..ba8a49f7f 100644 --- a/ccm-core/src/main/java/com/arsdigita/bebop/page/BebopApplicationServlet.java +++ b/ccm-core/src/main/java/com/arsdigita/bebop/page/BebopApplicationServlet.java @@ -25,7 +25,9 @@ import com.arsdigita.util.Assert; import com.arsdigita.web.BaseApplicationServlet; import com.arsdigita.xml.Document; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.shiro.subject.Subject; import org.libreccm.web.CcmApplication; import java.io.IOException; @@ -34,6 +36,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import javax.enterprise.inject.spi.CDI; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -61,7 +64,7 @@ public class BebopApplicationServlet extends BaseApplicationServlet { private static final long serialVersionUID = -6004503025521189639L; - private static final Logger s_log = Logger.getLogger( + private static final Logger LOGGER = LogManager.getLogger( BebopApplicationServlet.class); /** @@ -149,6 +152,11 @@ public class BebopApplicationServlet extends BaseApplicationServlet { final String pathInfo = sreq.getPathInfo(); Assert.exists(pathInfo, "String pathInfo"); + final Subject subject = CDI.current().select(Subject.class).get(); + LOGGER.debug("Current session is: {}", sreq.getSession().getId()); + LOGGER.debug("Current Shiro session is {}", + subject.getSession().getId().toString()); + final Page page = (Page) m_pages.get(pathInfo); if (page == null) { diff --git a/ccm-core/src/main/java/com/arsdigita/kernel/KernelConfig.java b/ccm-core/src/main/java/com/arsdigita/kernel/KernelConfig.java index 528c5bd0e..5e3f2d79a 100644 --- a/ccm-core/src/main/java/com/arsdigita/kernel/KernelConfig.java +++ b/ccm-core/src/main/java/com/arsdigita/kernel/KernelConfig.java @@ -70,9 +70,11 @@ public final class KernelConfig { private String defaultLanguage = "en"; public static KernelConfig getConfig() { - final CdiUtil cdiUtil = new CdiUtil(); - final ConfigurationManager confManager = cdiUtil.findBean( - ConfigurationManager.class); +// final CdiUtil cdiUtil = new CdiUtil(); +// final ConfigurationManager confManager = cdiUtil.findBean( +// ConfigurationManager.class); + final ConfigurationManager confManager = CdiUtil.createCdiUtil() + .findBean(ConfigurationManager.class); return confManager.findConfiguration(KernelConfig.class); } @@ -259,9 +261,10 @@ public final class KernelConfig { if (supportedLanguages == null) { languages = ""; } else { - languages = supportedLanguages.stream().collect(Collectors.joining(", ")); + languages = supportedLanguages.stream().collect(Collectors.joining( + ", ")); } - + return String.format( "%s{ " + "debugEnabled = %b, " diff --git a/ccm-core/src/main/java/com/arsdigita/kernel/security/SecurityConfig.java b/ccm-core/src/main/java/com/arsdigita/kernel/security/SecurityConfig.java index c2a302e53..8f609f54d 100644 --- a/ccm-core/src/main/java/com/arsdigita/kernel/security/SecurityConfig.java +++ b/ccm-core/src/main/java/com/arsdigita/kernel/security/SecurityConfig.java @@ -56,9 +56,8 @@ public final class SecurityConfig { private Integer hashIterations = 50000; public static SecurityConfig getConfig() { - final CdiUtil cdiUtil = new CdiUtil(); - final ConfigurationManager confManager = cdiUtil.findBean( - ConfigurationManager.class); + final ConfigurationManager confManager = CdiUtil.createCdiUtil() + .findBean(ConfigurationManager.class); return confManager.findConfiguration(SecurityConfig.class); } diff --git a/ccm-core/src/main/java/com/arsdigita/ui/admin/AdminServlet.java b/ccm-core/src/main/java/com/arsdigita/ui/admin/AdminServlet.java index c64f275b1..67b1e1d90 100644 --- a/ccm-core/src/main/java/com/arsdigita/ui/admin/AdminServlet.java +++ b/ccm-core/src/main/java/com/arsdigita/ui/admin/AdminServlet.java @@ -30,6 +30,8 @@ import com.arsdigita.web.BaseApplicationServlet; import com.arsdigita.web.LoginSignal; import com.arsdigita.xml.Document; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.apache.shiro.subject.Subject; import org.libreccm.security.PermissionChecker; import org.libreccm.web.CcmApplication; @@ -37,15 +39,16 @@ import org.libreccm.web.CcmApplication; import java.io.IOException; import java.util.HashMap; import java.util.Map; + import javax.enterprise.inject.spi.CDI; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServletRequest; -import +import javax.servlet.http.HttpServletResponse; - javax.servlet.http.HttpServletResponse; import org.libreccm.cdi.utils.CdiUtil; +import org.libreccm.configuration.ConfigurationManager; import static com.arsdigita.ui.admin.AdminConstants.*; @@ -65,12 +68,14 @@ import static com.arsdigita.ui.admin.AdminConstants.*; * @author pb */ @WebServlet(urlPatterns = {ADMIN_SERVLET_PATH}) -public class AdminServlet +public class AdminServlet extends BaseApplicationServlet implements AdminConstants { private static final long serialVersionUID = -3912367600768871630L; + private static final Logger LOGGER = LogManager.getLogger(AdminServlet.class); + /** * Logger instance for debugging */ @@ -111,21 +116,40 @@ public class AdminServlet ServletException, IOException { // /////// Some preparational steps /////////////// /* Determine access privilege: only logged in users may access */ - final CdiUtil cdiUtil = new CdiUtil(); +// final CdiUtil cdiUtil = new CdiUtil(); // final Subject subject = cdiUtil.findBean(Subject.class); final Subject subject = CDI.current().select(Subject.class).get(); - final PermissionChecker permissionChecker = cdiUtil.findBean( - PermissionChecker.class); +// final PermissionChecker permissionChecker = cdiUtil.findBean( +// PermissionChecker.class); + final PermissionChecker permissionChecker = CDI.current().select( + PermissionChecker.class).get(); + final ConfigurationManager confManager = CDI.current().select(ConfigurationManager.class).get(); + if (confManager == null) { + throw new IllegalStateException(); + } + + LOGGER.debug("Checking if subject {} is authenticated...", + subject.toString()); + LOGGER.debug("Current session is: {}", sreq.getSession().getId()); + LOGGER.debug("Current Shiro session is {}", + subject.getSession().getId().toString()); if (!subject.isAuthenticated()) { + LOGGER.debug("Subject {} is not authenticated, redirecting to login...", + subject.toString()); throw new LoginSignal(sreq); } + /* Determine access privilege: Admin privileges must be granted */ + LOGGER.debug("Subject is loggedin, checking if subject has required permissions..."); if (!permissionChecker.isPermitted("admin")) { + LOGGER.debug("Subject does *not* have required permissions. " + + "Access denied."); throw new AccessDeniedException("User is not an administrator"); } - + + LOGGER.debug("Serving admin page..."); /* Want admin to always show the latest stuff... */ DispatcherHelper.cacheDisable(sresp); diff --git a/ccm-core/src/main/java/com/arsdigita/ui/login/ChangePasswordForm.java b/ccm-core/src/main/java/com/arsdigita/ui/login/ChangePasswordForm.java index fe1772ad8..f8e071845 100644 --- a/ccm-core/src/main/java/com/arsdigita/ui/login/ChangePasswordForm.java +++ b/ccm-core/src/main/java/com/arsdigita/ui/login/ChangePasswordForm.java @@ -122,7 +122,7 @@ public class ChangePasswordForm extends Form m_returnURL.setPassIn(true); add(m_returnURL); - final CdiUtil cdiUtil = new CdiUtil(); + final CdiUtil cdiUtil = CdiUtil.createCdiUtil(); final Subject subject = cdiUtil.findBean(Subject.class); final Shiro shiro = cdiUtil.findBean(Shiro.class); diff --git a/ccm-core/src/main/java/com/arsdigita/ui/login/UserLoginForm.java b/ccm-core/src/main/java/com/arsdigita/ui/login/UserLoginForm.java index cfa89236e..2299bd253 100644 --- a/ccm-core/src/main/java/com/arsdigita/ui/login/UserLoginForm.java +++ b/ccm-core/src/main/java/com/arsdigita/ui/login/UserLoginForm.java @@ -143,25 +143,27 @@ public class UserLoginForm extends Form implements LoginConstants, // final ConfigurationManager confManager = CDI.current().select( // ConfigurationManager.class).get(); - final BeanManager beanManager = CDI.current().getBeanManager(); - final Set> beans = beanManager.getBeans( - ConfigurationManager.class); - final Iterator> iterator = beans.iterator(); - final ConfigurationManager confManager; - if (iterator.hasNext()) { - @SuppressWarnings("unchecked") - final Bean bean - = (Bean) iterator - .next(); - final CreationalContext ctx = beanManager. - createCreationalContext(bean); - - confManager = (ConfigurationManager) beanManager.getReference( - bean, ConfigurationManager.class, ctx); - } else { - throw new UncheckedWrapperException( - "Failed to lookup ConfigurationManager"); - } +// final BeanManager beanManager = CDI.current().getBeanManager(); +// final Set> beans = beanManager.getBeans( +// ConfigurationManager.class); +// final Iterator> iterator = beans.iterator(); +// final ConfigurationManager confManager; +// if (iterator.hasNext()) { +// @SuppressWarnings("unchecked") +// final Bean bean +// = (Bean) iterator +// .next(); +// final CreationalContext ctx = beanManager. +// createCreationalContext(bean); +// +// confManager = (ConfigurationManager) beanManager.getReference( +// bean, ConfigurationManager.class, ctx); +// } else { +// throw new UncheckedWrapperException( +// "Failed to lookup ConfigurationManager"); +// } + final ConfigurationManager confManager = CdiUtil.createCdiUtil() + .findBean(ConfigurationManager.class); securityConfig = confManager.findConfiguration(SecurityConfig.class); setMethod(Form.POST); @@ -385,12 +387,19 @@ public class UserLoginForm extends Form implements LoginConstants, ); token.setRememberMe(getPersistentLoginValue(state, false)); try { + LOGGER.debug("Trying to login user {}...", subject.toString()); subject.login(token); } catch (AuthenticationException ex) { onLoginFail(event, ex); } LOGGER.debug("User {} logged in successfully.", token.getUsername()); + LOGGER.debug("subject = {}", subject.toString()); + LOGGER.debug("Current session is: {}", + state.getRequest().getSession().getId()); + LOGGER.debug("Current Shiro session is {}", + subject.getSession().getId().toString()); + } /** diff --git a/ccm-core/src/main/java/com/arsdigita/web/URL.java b/ccm-core/src/main/java/com/arsdigita/web/URL.java index a9b974b8f..aa542690a 100644 --- a/ccm-core/src/main/java/com/arsdigita/web/URL.java +++ b/ccm-core/src/main/java/com/arsdigita/web/URL.java @@ -21,15 +21,28 @@ package com.arsdigita.web; import com.arsdigita.dispatcher.DispatcherHelper; //import com.arsdigita.kernel.security.Util; import com.arsdigita.util.Assert; +import com.arsdigita.util.UncheckedWrapperException; import com.arsdigita.util.servlet.HttpHost; +import oracle.jrockit.jfr.tools.ConCatRepository; + import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.apache.log4j.Logger; +import org.libreccm.configuration.ConfigurationManager; import org.libreccm.web.CcmApplication; +import java.util.Iterator; +import java.util.Set; + +import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.BeanManager; +import javax.naming.InitialContext; +import javax.naming.NamingException; + /** *

* URL models a future request according to the servlet worldview. Its principal @@ -516,8 +529,8 @@ public class URL { * starts with a "/". For example, "/ccm/forum/thread.jsp".

* *

- * This method is defined to return the equivalent of * getWebContextPath() + getServletPath() + - getPathInfo().

+ * This method is defined to return the equivalent of * getWebContextPath() + getServletPath() + + * getPathInfo().

* * @see javax.servlet.http.HttpServletRequest#getRequestURI() * @return a String comprised of the context path, servlet @@ -679,7 +692,37 @@ public class URL { public static final URL there(final HttpServletRequest sreq, final String path, final ParameterMap params) { - final WebConfig config = Web.getConfig(); + final BeanManager beanManager; + try { + final InitialContext context = new InitialContext(); + beanManager = (BeanManager) context.lookup( + "java:comp/BeanManager"); + + } catch (NamingException ex) { + throw new UncheckedWrapperException(ex); + } + + final Set> beans = beanManager.getBeans( + ConfigurationManager.class); + final Iterator> iterator = beans.iterator(); + final ConfigurationManager confManager; + if (iterator.hasNext()) { + @SuppressWarnings("unchecked") + final Bean bean + = (Bean) iterator + .next(); + final CreationalContext ctx = beanManager + .createCreationalContext(bean); + confManager = (ConfigurationManager) beanManager.getReference( + bean, ConfigurationManager.class, ctx); + } else { + throw new IllegalStateException("No configuration manager"); + } + +// final ConfigurationManager confManager = CDI.current().select( +// ConfigurationManager.class).get(); + final WebConfig config = confManager.findConfiguration(WebConfig.class); +// final WebConfig config = Web.getConfig(); Assert.exists(sreq, "HttpServletRequest sreq"); Assert.exists(config, "WebConfig config"); @@ -797,8 +840,8 @@ public class URL { if (pathInfo == null) { return URL.there(sreq, app.getPrimaryUrl().toString(), params); } else { - return URL.there(sreq, app.getPrimaryUrl().toString() + pathInfo, - params); + return URL.there(sreq, app.getPrimaryUrl().toString() + pathInfo, + params); } } @@ -913,7 +956,7 @@ public class URL { static URL login(final HttpServletRequest sreq) { //Replace register eventuelly... - return URL.excursion(sreq, + return URL.excursion(sreq, "/register/", (ParameterMap) s_empty.get()); } diff --git a/ccm-core/src/main/java/com/arsdigita/web/WebConfig.java b/ccm-core/src/main/java/com/arsdigita/web/WebConfig.java index 03781ef07..1a5052d7c 100644 --- a/ccm-core/src/main/java/com/arsdigita/web/WebConfig.java +++ b/ccm-core/src/main/java/com/arsdigita/web/WebConfig.java @@ -84,29 +84,32 @@ public final class WebConfig { private String dynamicHostProviderClass; public static WebConfig getConfig() { - final BeanManager beanManager = CDI.current().getBeanManager(); - final Set> beans = beanManager.getBeans( - ConfigurationManager.class); - final Iterator> iterator = beans.iterator(); - final ConfigurationManager confManager; - if (iterator.hasNext()) { - @SuppressWarnings("unchecked") - final Bean bean - = (Bean) iterator - .next(); - final CreationalContext ctx = beanManager - .createCreationalContext(bean); + final ConfigurationManager confManager = CDI.current().select( + ConfigurationManager.class).get(); - confManager = (ConfigurationManager) beanManager.getReference( - bean, ConfigurationManager.class, ctx); - } else { - LOGGER.error(new ParameterizedMessage( - "No CDI Bean for type {} found.", - ConfigurationManager.class.getName())); - throw new IllegalStateException(String.format( - "No CDI Bean for type \"%s\" found", - ConfigurationManager.class.getName())); - } +// final BeanManager beanManager = CDI.current().getBeanManager(); +// final Set> beans = beanManager.getBeans( +// ConfigurationManager.class); +// final Iterator> iterator = beans.iterator(); +// final ConfigurationManager confManager; +// if (iterator.hasNext()) { +// @SuppressWarnings("unchecked") +// final Bean bean +// = (Bean) iterator +// .next(); +// final CreationalContext ctx = beanManager +// .createCreationalContext(bean); +// +// confManager = (ConfigurationManager) beanManager.getReference( +// bean, ConfigurationManager.class, ctx); +// } else { +// LOGGER.error(new ParameterizedMessage( +// "No CDI Bean for type {} found.", +// ConfigurationManager.class.getName())); +// throw new IllegalStateException(String.format( +// "No CDI Bean for type \"%s\" found", +// ConfigurationManager.class.getName())); +// } // final CdiUtil cdiUtil = new CdiUtil(); // final ConfigurationManager confManager = cdiUtil.findBean( diff --git a/ccm-core/src/main/java/org/libreccm/categorization/Domain.java b/ccm-core/src/main/java/org/libreccm/categorization/Domain.java index 0db23b929..70b4a9aa4 100644 --- a/ccm-core/src/main/java/org/libreccm/categorization/Domain.java +++ b/ccm-core/src/main/java/org/libreccm/categorization/Domain.java @@ -24,9 +24,9 @@ import static org.libreccm.core.CoreConstants.*; import org.hibernate.validator.constraints.NotBlank; import org.hibernate.validator.constraints.URL; import org.libreccm.core.CcmObject; +import org.libreccm.core.DefaultEntityGraph; import org.libreccm.l10n.LocalizedString; import org.libreccm.web.CcmApplication; -import org.omg.CORBA.DomainManager; import java.io.Serializable; import java.util.ArrayList; @@ -91,6 +91,7 @@ import javax.xml.bind.annotation.XmlRootElement; @NamedAttributeNode("subCategories") })}) }) +@DefaultEntityGraph("Domain.allCategories") @XmlRootElement(name = "domain", namespace = CAT_XML_NS) public class Domain extends CcmObject implements Serializable { diff --git a/ccm-core/src/main/java/org/libreccm/cdi/utils/CdiUtil.java b/ccm-core/src/main/java/org/libreccm/cdi/utils/CdiUtil.java index b8cc28630..640c2934f 100644 --- a/ccm-core/src/main/java/org/libreccm/cdi/utils/CdiUtil.java +++ b/ccm-core/src/main/java/org/libreccm/cdi/utils/CdiUtil.java @@ -29,6 +29,8 @@ import javax.enterprise.context.spi.CreationalContext; import javax.enterprise.inject.spi.Bean; import javax.enterprise.inject.spi.BeanManager; import javax.enterprise.inject.spi.CDI; +import javax.naming.InitialContext; +import javax.naming.NamingException; /** * @@ -43,7 +45,24 @@ public class CdiUtil { public CdiUtil() { beanManager = CDI.current().getBeanManager(); } + + private CdiUtil(final BeanManager beanManager) { + this.beanManager = beanManager; + } + public static CdiUtil createCdiUtil() { + try { + final InitialContext context = new InitialContext(); + final BeanManager beanManager = (BeanManager) context.lookup( + "java:comp/BeanManager"); + return new CdiUtil(beanManager); + } catch(NamingException ex) { + throw new IllegalStateException("Unable to lookup BeanManager.", ex); + } + + + } + @SuppressWarnings("unchecked") public T findBean(final Class beanType) { final Set> beans = beanManager.getBeans(beanType); diff --git a/ccm-core/src/main/java/org/libreccm/core/AbstractEntityRepository.java b/ccm-core/src/main/java/org/libreccm/core/AbstractEntityRepository.java index 3ded6522f..03b98980d 100644 --- a/ccm-core/src/main/java/org/libreccm/core/AbstractEntityRepository.java +++ b/ccm-core/src/main/java/org/libreccm/core/AbstractEntityRepository.java @@ -18,13 +18,18 @@ */ package org.libreccm.core; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import javax.inject.Inject; import javax.persistence.EntityGraph; import javax.persistence.EntityManager; +import javax.persistence.Query; import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; @@ -39,7 +44,11 @@ import javax.persistence.criteria.Root; */ public abstract class AbstractEntityRepository { - static final String FETCH_GRAPH_HINT_KEY = "javax.persistence.fetchgraph"; + private static final Logger LOGGER = LogManager.getLogger( + AbstractEntityRepository.class); + + protected static final String FETCH_GRAPH_HINT_KEY + = "javax.persistence.fetchgraph"; /** * The {@link EntityManager} instance to use. Provided by the container via @@ -57,12 +66,99 @@ public abstract class AbstractEntityRepository { return entityManager; } + /** + * Create an {@link EntityGraph} for the entity class of this repository. + * + * For more details about entity graphs/fetch graphs refer to the JPA + * documentation. Internally this method uses + * {@link EntityManager#createEntityGraph(java.lang.Class)}. + * + * @return An EntityGraph for this entity graph. + */ + public EntityGraph createEntityGraph() { + return entityManager.createEntityGraph(getEntityClass()); + } + + protected void applyDefaultEntityGraph(final TypedQuery query) { + if (getEntityClass().isAnnotationPresent(DefaultEntityGraph.class)) { + LOGGER.debug("The following EntityGraphs are available for the " + + "entity class {}:", + getEntityClass().getName()); + getEntityManager().getEntityGraphs(getEntityClass()).stream() + .forEach(g -> LOGGER.debug("\t{}", g.getName())); + LOGGER.debug("Entity class {} has default entity graphs:", + getEntityClass().getName()); + LOGGER.debug("Applying entity graph {}", + getEntityClass().getAnnotation( + DefaultEntityGraph.class).value()); + query.setHint(FETCH_GRAPH_HINT_KEY, + entityManager.getEntityGraph( + getEntityClass().getAnnotation( + DefaultEntityGraph.class).value())); + } + } + + /** + * Helper method for retrieving a single result from a query. + * + * @param query The query from which the result is retrieved. + * + * @return A first result or the query or {@code null} of there is no + * result. + */ + protected E getSingleResultOrNull(final TypedQuery query) { + final List result = query.getResultList(); + if (result.isEmpty()) { + return null; + } else { + return result.get(0); + } + } + + /** + * Helper method for retrieving a single result from a query. In contrast to + * {@link #getSingleResultOrNull(javax.persistence.TypedQuery)} this method + * return an {@link Optional} for the result. + * + * @param query The query from which the result is retrieved. + * + * @return An {@link Optional} instance wrapping the first result of the + * query. If there is no result the {@code Optional} is empty. + */ + protected Optional getSingleResult(final TypedQuery query) { + final List result = query.getResultList(); + if (result.isEmpty()) { + return Optional.empty(); + } else { + return Optional.of(result.get(0)); + } + } + + /** + * Creates a mutable copy of a named entity graph which an be further + * customised. + * + * Internally this method uses + * {@link EntityManager#createEntityGraph(java.lang.String)}. + * + * @param entityGraphName The name of the named entity graph. + * + * @return A mutable copy of the named entity graph identified by the + * provided name or {@code null} if there is no such named entity + * graph. + */ + @SuppressWarnings("unchecked") + public EntityGraph createEntityGraph(final String entityGraphName) { + return (EntityGraph) entityManager.createEntityGraph( + entityGraphName); + } + /** * The class of entities for which this repository can be used. For creating * a repository class overwrite this method. * * @return The {@code Class} of the Entity which are managed by this - * repository. + * repository. */ public abstract Class getEntityClass(); @@ -72,15 +168,21 @@ public abstract class AbstractEntityRepository { * @param entityId The ID of the entity to retrieve. * * @return The entity identified by the provided ID of {@code null} if there - * is no such entity. + * is no such entity. */ public E findById(final K entityId) { - return entityManager.find(getEntityClass(), entityId); + if (getEntityClass().isAnnotationPresent(DefaultEntityGraph.class)) { + return findById(entityId, getEntityClass().getAnnotation( + DefaultEntityGraph.class).value()); + } else { + return entityManager.find(getEntityClass(), entityId); + } } public E findById(final K entityId, final String entityGraphName) { + @SuppressWarnings("unchecked") final EntityGraph entityGraph = (EntityGraph) entityManager. - getEntityGraph(entityGraphName); + getEntityGraph(entityGraphName); return findById(entityId, entityGraph); } @@ -95,15 +197,15 @@ public abstract class AbstractEntityRepository { * responsible for. * * @return The list of entities in the database which are of the type - * provided by {@link #getEntityClass()}. + * provided by {@link #getEntityClass()}. */ public List findAll() { // We are using the Critiera API here because otherwise we can't // pass the type of the entity dynmacially. final CriteriaBuilder criteriaBuilder = entityManager - .getCriteriaBuilder(); + .getCriteriaBuilder(); final CriteriaQuery criteriaQuery = criteriaBuilder.createQuery( - getEntityClass()); + getEntityClass()); final Root root = criteriaQuery.from(getEntityClass()); criteriaQuery.select(root); @@ -117,8 +219,9 @@ public abstract class AbstractEntityRepository { * entity is a a new one. * * @param entity The entity to check. + * * @return {@code true} if the entity is new (isn't in the database yet), - * {@code false} otherwise. + * {@code false} otherwise. */ public abstract boolean isNew(final E entity); diff --git a/ccm-core/src/main/java/org/libreccm/core/DefaultEntityGraph.java b/ccm-core/src/main/java/org/libreccm/core/DefaultEntityGraph.java new file mode 100644 index 000000000..2b90c28bf --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/core/DefaultEntityGraph.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2016 LibreCCM Foundation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.libreccm.core; + +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; + +/** + * + * @author Jens Pelzetter + */ +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Target({TYPE}) +public @interface DefaultEntityGraph { + + String value(); + +} diff --git a/ccm-core/src/main/java/org/libreccm/security/Party.java b/ccm-core/src/main/java/org/libreccm/security/Party.java index c5cba8d6b..32c544fb8 100644 --- a/ccm-core/src/main/java/org/libreccm/security/Party.java +++ b/ccm-core/src/main/java/org/libreccm/security/Party.java @@ -20,6 +20,8 @@ package org.libreccm.security; import static org.libreccm.core.CoreConstants.*; +import org.libreccm.core.DefaultEntityGraph; + import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; @@ -33,6 +35,9 @@ import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Inheritance; import javax.persistence.InheritanceType; +import javax.persistence.NamedAttributeNode; +import javax.persistence.NamedEntityGraph; +import javax.persistence.NamedEntityGraphs; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.OneToMany; @@ -56,6 +61,12 @@ import javax.xml.bind.annotation.XmlElementWrapper; @NamedQuery(name = "Party.findByName", query = "SELECT p FROM Party p WHERE p.name = :name") }) +@NamedEntityGraphs({ + @NamedEntityGraph(name = "Party.withRoleMemberships", + attributeNodes = @NamedAttributeNode( + value = "roleMemberships")) +}) +@DefaultEntityGraph("Party.withRoleMemberships") public class Party implements Serializable { private static final long serialVersionUID = 3319997992281332204L; diff --git a/ccm-core/src/main/java/org/libreccm/security/Shiro.java b/ccm-core/src/main/java/org/libreccm/security/Shiro.java index b90dee548..cbf2310a9 100644 --- a/ccm-core/src/main/java/org/libreccm/security/Shiro.java +++ b/ccm-core/src/main/java/org/libreccm/security/Shiro.java @@ -62,7 +62,8 @@ public class Shiro { @Produces @Named("securityManager") public SecurityManager getSecurityManager() { - return proxy(SecurityManager.class, new SubjectInvocationHandler()); + return proxy(SecurityManager.class, + new SecurityManagerInvocationHandler()); } /** @@ -115,6 +116,7 @@ public class Shiro { return publicUser; } + @SuppressWarnings("unchecked") private T proxy(final Class clazz, final InvocationHandler handler) { return (T) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{clazz}, diff --git a/ccm-core/src/main/java/org/libreccm/security/User.java b/ccm-core/src/main/java/org/libreccm/security/User.java index 44d5f1848..e92577363 100644 --- a/ccm-core/src/main/java/org/libreccm/security/User.java +++ b/ccm-core/src/main/java/org/libreccm/security/User.java @@ -20,6 +20,7 @@ package org.libreccm.security; import static org.libreccm.core.CoreConstants.*; +import org.libreccm.core.DefaultEntityGraph; import org.libreccm.core.EmailAddress; import java.io.Serializable; @@ -37,9 +38,13 @@ import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; +import javax.persistence.NamedAttributeNode; +import javax.persistence.NamedEntityGraph; +import javax.persistence.NamedEntityGraphs; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.OneToMany; +import javax.persistence.OrderColumn; import javax.persistence.Table; import javax.validation.constraints.NotNull; import javax.xml.bind.annotation.XmlElement; @@ -59,9 +64,18 @@ import javax.xml.bind.annotation.XmlTransient; @NamedQuery(name = "User.findByName", query = "SELECT u FROM User u WHERE u.name = :name"), @NamedQuery(name = "User.findByEmailAddress", - query = "SELECT u FROM User u WHERE " + - "u.primaryEmailAddress.address = :emailAddress") + query = "SELECT u FROM User u WHERE " + + "u.primaryEmailAddress.address = :emailAddress") }) +@NamedEntityGraphs({ + @NamedEntityGraph(name = "User.withGroupAndRoleMemberships", + attributeNodes = { + @NamedAttributeNode( + value = "groupMemberships"), + @NamedAttributeNode( + value = "roleMemberships")}) +}) +@DefaultEntityGraph("User.withGroupAndRoleMemberships") @XmlRootElement(name = "user", namespace = CORE_XML_NS) //Supressing a few warnings from PMD because they misleading here. //User is perfectly fine class name, and the complexity is not to high... @@ -112,9 +126,9 @@ public class User extends Party implements Serializable { private List emailAddresses; /** - * A user can be banned which means that he or she can't login into - * the system anymore. We use this approach rather than simply deleting users - * to preserve the edit history of several objects. + * A user can be banned which means that he or she can't login into the + * system anymore. We use this approach rather than simply deleting users to + * preserve the edit history of several objects. */ @Column(name = "BANNED") @XmlElement(name = "banned", namespace = CORE_XML_NS) diff --git a/ccm-core/src/main/java/org/libreccm/security/UserRepository.java b/ccm-core/src/main/java/org/libreccm/security/UserRepository.java index 693a1c98d..41c523895 100644 --- a/ccm-core/src/main/java/org/libreccm/security/UserRepository.java +++ b/ccm-core/src/main/java/org/libreccm/security/UserRepository.java @@ -21,8 +21,10 @@ package org.libreccm.security; import org.libreccm.core.AbstractEntityRepository; import java.util.List; +import java.util.Optional; import javax.enterprise.context.RequestScoped; +import javax.persistence.EntityGraph; import javax.persistence.TypedQuery; /** @@ -52,20 +54,59 @@ public class UserRepository extends AbstractEntityRepository { * @param name The name of the user to find. * * @return The user identified by the provided name. If there are multiple - * user matching the user name (should be possible) the first one is - * returned. If there is no matching user {@code null} is returned. + * user matching the user name (should be possible) the first one is + * returned. If there is no matching user {@code null} is returned. */ public User findByName(final String name) { final TypedQuery query = getEntityManager().createNamedQuery( - "User.findByName", - User.class); + "User.findByName", User.class); + applyDefaultEntityGraph(query); query.setParameter("name", name); - final List result = query.getResultList(); - if (result.isEmpty()) { - return null; - } else { - return result.get(0); - } + + return getSingleResultOrNull(query); + +// final List result = query.getResultList(); +// if (result.isEmpty()) { +// return null; +// } else { +// return result.get(0); +// } + } + + /** + * Finds a user by its name and applies the given named entity graph to the + * query. + * + * @param name The name of the user to find. + * @param entityGraphName The named entity graph to use. + * + * @return The user identified by the provided name. If there are multiple + * user matching the user name (should be possible) the first one is + * returned. If there is no matching user {@code null} is returned. + */ + public User findByName(final String name, final String entityGraphName) { + @SuppressWarnings("unchecked") + final EntityGraph entityGraph + = (EntityGraph) getEntityManager() + .getEntityGraph(entityGraphName); + return findByName(name, entityGraph); + } + + public User findByName(final String name, + final EntityGraph entityGraph) { + final TypedQuery query = getEntityManager().createNamedQuery( + "User.findByName", User.class); + query.setParameter("name", name); + query.setHint(FETCH_GRAPH_HINT_KEY, entityGraph); + + return getSingleResultOrNull(query); + +// final List result = query.getResultList(); +// if (result.isEmpty()) { +// return null; +// } else { +// return result.get(0); +// } } /** @@ -74,19 +115,42 @@ public class UserRepository extends AbstractEntityRepository { * @param emailAddress The email address which identifies the user. * * @return The user identified by the provided email address. If there are - * multiple matching users only the first one is returned. If there is no - * matching user {@code null} is returned. + * multiple matching users only the first one is returned. If there + * is no matching user {@code null} is returned. */ public User findByEmailAddress(final String emailAddress) { final TypedQuery query = getEntityManager().createNamedQuery( - "User.findByEmailAddress", User.class); + "User.findByEmailAddress", User.class); query.setParameter("emailAddress", emailAddress); - final List result = query.getResultList(); - if (result.isEmpty()) { - return null; - } else { - return result.get(0); - } + applyDefaultEntityGraph(query); + + return getSingleResultOrNull(query); + +// final List result = query.getResultList(); +// if (result.isEmpty()) { +// return null; +// } else { +// return result.get(0); +// } } - + + public User findByEmailAddress(final String emailAddress, + final String entityGraphName) { + @SuppressWarnings("unchecked") + final EntityGraph entityGraph + = (EntityGraph) getEntityManager() + .getEntityGraph(entityGraphName); + return findByEmailAddress(emailAddress, entityGraph); + } + + public User findByEmailAddress(final String emailAddress, + final EntityGraph entityGraph) { + final TypedQuery query = getEntityManager().createNamedQuery( + "User.findByEmailAddress", User.class); + query.setParameter("emailAddress", emailAddress); + query.setHint(FETCH_GRAPH_HINT_KEY, entityGraph); + + return getSingleResultOrNull(query); + } + }