Java tutorial
/* * Copyright 2008-2016 Hippo B.V. (http://www.onehippo.com) * * Licensed 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 * * 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.hippoecm.frontend.session; import java.util.HashMap; import java.util.Map; import javax.jcr.Credentials; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.observation.ObservationManager; import javax.jcr.query.QueryManager; import javax.security.auth.login.AccountExpiredException; import javax.security.auth.login.CredentialExpiredException; import javax.security.auth.login.FailedLoginException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.apache.cxf.common.util.StringUtils; import org.apache.wicket.Application; import org.apache.wicket.Component; import org.apache.wicket.model.IModel; import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.protocol.http.servlet.ServletWebRequest; import org.apache.wicket.request.Request; import org.apache.wicket.request.cycle.RequestCycle; import org.hippoecm.frontend.Home; import org.hippoecm.frontend.Main; import org.hippoecm.frontend.PluginApplication; import org.hippoecm.frontend.RepositoryUnavailableException; import org.hippoecm.frontend.model.JcrSessionModel; import org.hippoecm.frontend.model.UserCredentials; import org.hippoecm.frontend.observation.FacetRootsObserver; import org.hippoecm.frontend.observation.JcrObservationManager; import org.hippoecm.frontend.plugin.IPlugin; import org.hippoecm.frontend.plugin.config.IPluginConfigService; import org.hippoecm.frontend.plugin.config.impl.IApplicationFactory; import org.hippoecm.frontend.session.LoginException.Cause; import org.hippoecm.hst.diagnosis.HDC; import org.hippoecm.hst.diagnosis.Task; import org.hippoecm.repository.HippoRepository; import org.hippoecm.repository.api.HippoNode; import org.hippoecm.repository.api.HippoSession; import org.hippoecm.repository.api.HippoWorkspace; import org.hippoecm.repository.api.WorkflowManager; import org.onehippo.cms7.services.cmscontext.CmsSessionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.hippoecm.frontend.Main.PLUGIN_APPLICATION_VALUE_CMS; /** * A Wicket {@link org.apache.wicket.Session} that maintains a reference to a JCR {@link javax.jcr.Session}. It is * available to plugins as a threadlocal variable during request processing. * <p/> * When the Wicket session is no longer referenced, the JCR session model is detached. */ public class PluginUserSession extends UserSession { private static final long serialVersionUID = 1L; private static final Logger log = LoggerFactory.getLogger(UserSession.class); public static final String SESSION_CMS_APP_COUNT = "session-cms:appCount"; private static UserCredentials fallbackCredentials; private IModel<Session> jcrSessionModel; private transient Session fallbackSession; private final IModel<ClassLoader> classLoader; private final IModel<WorkflowManager> workflowManager; private transient FacetRootsObserver facetRootsObserver; private UserCredentials credentials; private Map<String, Integer> pluginComponentCounters = new HashMap<>(); @SuppressWarnings("unused") private String sessionId; private int pageId; public UserCredentials getUserCredentials() { return credentials; } public static void setCredentials(UserCredentials credentials) throws RepositoryException { fallbackCredentials = credentials; } public static PluginUserSession get() { return (PluginUserSession) UserSession.get(); } public PluginUserSession(Request request) { super(request); classLoader = new LoadableDetachableModel<ClassLoader>() { private static final long serialVersionUID = 1L; @Override protected ClassLoader load() { Session session = getJcrSessionInternal(); if (session != null) { try { return ((HippoSession) session).getSessionClassLoader(); } catch (RepositoryException ex) { log.error(ex.getClass().getName() + ": " + ex.getMessage(), ex); } } return null; } }; workflowManager = new LoadableDetachableModel<WorkflowManager>() { private static final long serialVersionUID = 1L; @Override protected WorkflowManager load() { Session jcrSession = getJcrSessionInternal(); if (jcrSession != null) { try { HippoWorkspace workspace = (HippoWorkspace) jcrSession.getWorkspace(); return workspace.getWorkflowManager(); } catch (RepositoryException ex) { ex.printStackTrace(); } } return null; } }; //Calling the dirty() method causes this wicket session to be reset in the http session //so that it knows that the wicket session has changed (we've just added the jcr session model etc.) dirty(); } PluginUserSession(Request request, LoadableDetachableModel<Session> jcrSessionModel) { super(request); classLoader = new LoadableDetachableModel<ClassLoader>() { private static final long serialVersionUID = 1L; @Override protected ClassLoader load() { return null; } }; workflowManager = new LoadableDetachableModel<WorkflowManager>() { private static final long serialVersionUID = 1L; @Override protected WorkflowManager load() { return null; } }; try { login(null, jcrSessionModel); } catch (LoginException ignore) { } //Calling the dirty() method causes this wicket session to be reset in the http session //so that it knows that the wicket session has changed (we've just added the jcr session model etc.) dirty(); } /** * Retrieve the JCR {@link javax.jcr.Session} that is bound to the Wicket {@link org.apache.wicket.Session}. This * method will throw a RestartResponseException when no JCR session is available. */ public Session getJcrSession() { Session session = getJcrSessionInternal(); if (session == null) { Main main = (Main) Application.get(); if (fallbackCredentials == null) { try { main.getRepository(); // side effect of reinitializing fallback credentials } catch (RepositoryException ignored) { } } if (fallbackSession == null) { try { fallbackSession = JcrSessionModel.login(fallbackCredentials); } catch (RepositoryException e) { log.warn("Cannot login fallback session: " + e.getMessage()); } } session = fallbackSession; if (session == null) { main.resetConnection(); throw new RepositoryUnavailableException("Repository is not available."); } } else if (fallbackSession != null) { fallbackSession.logout(); fallbackSession = null; } return session; } private Session getJcrSessionInternal() { if (jcrSessionModel != null) { Session result = jcrSessionModel.getObject(); if (result == null) { return null; } if (!result.isLive()) { log.error("Found session in an invalid unallowed state: not live. Logout PluginUserSession."); logout(); try { // we need to trigger a repo exception to get in the stacktrace the cause which contains from where // the session was logged out result.getRootNode(); } catch (RepositoryException e) { // expected throw new InvalidSessionException("Invalid (non-live) session found.", e); } } return result; } return null; } /** * Release the JCR {@link javax.jcr.Session} that is bound to the Wicket session. The session model will take care * of saving any pending changes. Event listeners will remain registered and will re-register with a new session. */ public void releaseJcrSession() { if (jcrSessionModel != null) { try { jcrSessionModel.detach(); } catch (Exception e) { log.error("Error during detaching JCR Model", e); } } classLoader.detach(); workflowManager.detach(); facetRootsObserver = null; } public void login() { try { UserCredentials userCreds = getUserCredentialsFromRequestAttribute(); login(userCreds, new JcrSessionModel(userCreds)); } catch (LoginException ignore) { } } /** * {@link #login()} method invokes this method if there's any <code>UserCredentials</code> object from the request. * For example, Web SSO Agent can set a UserCredentials for the user as request attribute. */ protected UserCredentials getUserCredentialsFromRequestAttribute() { HttpServletRequest request = ((HttpServletRequest) RequestCycle.get().getRequest().getContainerRequest()); return (UserCredentials) request.getAttribute(UserCredentials.class.getName()); } @Override public void login(final String username, final String password) throws LoginException { UserCredentials credentials = new UserCredentials(username, password); this.login(credentials); } public void login(UserCredentials credentials) throws LoginException { login(credentials, new JcrSessionModel(credentials)); IApplicationFactory factory = getApplicationFactory(); final IPluginConfigService application = factory.getApplication(getApplicationName()); if (application != null && !application.isSaveOnExitEnabled()) { if (jcrSessionModel instanceof JcrSessionModel) { ((JcrSessionModel) jcrSessionModel).setSaveOnExit(false); } } } private void handleLoginException(javax.jcr.LoginException le) throws LoginException { final Throwable rootCause = le.getCause(); if (rootCause instanceof FailedLoginException) { throw new LoginException(Cause.INCORRECT_CREDENTIALS); } else if (rootCause instanceof CredentialExpiredException) { throw new LoginException(Cause.PASSWORD_EXPIRED); } else if (rootCause instanceof AccountExpiredException) { throw new LoginException(Cause.ACCOUNT_EXPIRED); } } void login(UserCredentials credentials, LoadableDetachableModel<Session> sessionModel) throws LoginException { classLoader.detach(); workflowManager.detach(); facetRootsObserver = null; Session jcrSession = null; if (sessionModel instanceof JcrSessionModel) { try { jcrSession = ((JcrSessionModel) sessionModel).getSessionObject(); } catch (javax.jcr.LoginException ex) { handleLoginException(ex); } } else { jcrSession = sessionModel.getObject(); } if (jcrSession == null) { throw new LoginException(Cause.ACCESS_DENIED); } checkApplicationPermission(jcrSession); IModel<Session> oldModel = jcrSessionModel; jcrSessionModel = sessionModel; this.credentials = credentials; if (oldModel != null) { oldModel.detach(); } if (credentials == null) { pageId = 0; } else { // Set the username to facilitate two-factor authentication filters getHttpSession().setAttribute("hippo:username", credentials.getUsername()); increaseAppCount(); pageId = 1; } } private HttpSession getHttpSession() { return ((ServletWebRequest) RequestCycle.get().getRequest()).getContainerRequest().getSession(); } protected void checkApplicationPermission(final Session jcrSession) throws LoginException { final String applicationName = getApplicationName(jcrSession); final IPluginConfigService application = getApplicationFactory(jcrSession).getApplication(applicationName); if (!application.checkPermission(jcrSession)) { throw new LoginException(Cause.ACCESS_DENIED); } } public void logout() { Task logoutTask = null; try { if (HDC.isStarted()) { logoutTask = HDC.getCurrentTask().startSubtask("PluginUserSession.logout"); } releaseJcrSession(); jcrSessionModel = null; JcrObservationManager.getInstance().cleanupListeners(this); pageId = 0; getHttpSession().removeAttribute("hippo:username"); final int appCount = decreaseAppCount(); dirty(); if (appCount == 0) { invalidate(); } else { if (PluginApplication.get().getPluginApplicationName().equals(PLUGIN_APPLICATION_VALUE_CMS)) { getHttpSession().removeAttribute(CmsSessionContext.SESSION_KEY); } } } finally { if (logoutTask != null) { logoutTask.stop(); } } } public Credentials getCredentials() { return credentials.getJcrCredentials(); } public int getPageId() { return pageId; } /** * Retrieve the JCR session classloader, if available, or null when this is not the case. */ public ClassLoader getClassLoader() { return classLoader.getObject(); } /** * Retrieve the Hippo workflow manager, if one is available. When none is, null is returned. */ public WorkflowManager getWorkflowManager() { return workflowManager.getObject(); } /** * Retrieve the JCR query manager, when one is available. When none is, null is returned. */ public QueryManager getQueryManager() { Session jcrSession = getJcrSessionInternal(); if (jcrSession != null) { try { return jcrSession.getWorkspace().getQueryManager(); } catch (RepositoryException e) { log.error(e.getMessage()); } } return null; } @Override public ObservationManager getObservationManager() { return JcrObservationManager.getInstance(); } public IApplicationFactory getApplicationFactory() { return getApplicationFactory(getJcrSession()); } private IApplicationFactory getApplicationFactory(Session session) { return ((Main) Main.get()).getApplicationFactory(session); } /** * Retrieve the JCR root node. Null is returned when no session is available or the root node cannot be obtained * from it. */ public HippoNode getRootNode() { HippoNode result = null; try { Session jcrSession = getJcrSessionInternal(); if (jcrSession != null) { result = (HippoNode) jcrSession.getRootNode(); } } catch (RepositoryException e) { log.error(e.getMessage()); } return result; } @Override public void detach() { if (fallbackSession != null) { fallbackSession.logout(); fallbackSession = null; } super.detach(); } public void flush() { Task flushTask = null; try { if (HDC.isStarted()) { flushTask = HDC.getCurrentTask().startSubtask("PluginUserSession.flush"); } JcrObservationManager.getInstance().cleanupListeners(this); if (Application.exists()) { clear(); } else { log.info("There is no wicket application attached to the current thread."); } } finally { if (flushTask != null) { flushTask.stop(); } } } @Override public void onInvalidate() { if (fallbackSession != null) { fallbackSession.logout(); fallbackSession = null; } releaseJcrSession(); JcrObservationManager.getInstance().cleanupListeners(this); } /** * THIS METHOD IS NOT PART OF THE PUBLIC API AND SHOULD NOT BE INVOKED BY PLUGINS */ public FacetRootsObserver getFacetRootsObserver() { if (facetRootsObserver == null) { facetRootsObserver = new FacetRootsObserver(); } return facetRootsObserver; } // Do not add the @Override annotation on this public Object getMarkupId(Component component) { String markupId = null; for (Component ancestor = component.getParent(); ancestor != null; ancestor = ancestor.getParent()) { if (ancestor instanceof IPlugin || ancestor instanceof Home) { markupId = ancestor.getMarkupId(true); break; } } if (markupId == null) { return "root"; } int componentNum = 0; if (pluginComponentCounters.containsKey(markupId)) { componentNum = pluginComponentCounters.get(markupId); } ++componentNum; pluginComponentCounters.put(markupId, componentNum); return markupId + "_" + componentNum; } public String getApplicationName() { return getApplicationName(getJcrSession()); } private String getApplicationName(Session session) { String applicationName; String userID = session.getUserID(); if (StringUtils.isEmpty(userID) || userID.equalsIgnoreCase("anonymous")) { applicationName = "login"; } else { applicationName = PluginApplication.get().getPluginApplicationName(); } return applicationName; } @Override public HippoRepository getHippoRepository() throws RepositoryException { Main main = (Main) Application.get(); return main.getRepository(); } private int increaseAppCount() { final Integer appCount = (Integer) getHttpSession().getAttribute(SESSION_CMS_APP_COUNT); if (appCount == null) { getHttpSession().setAttribute(SESSION_CMS_APP_COUNT, new Integer(1)); return 1; } else { final int newValue = appCount.intValue() + 1; getHttpSession().setAttribute(SESSION_CMS_APP_COUNT, new Integer(newValue)); return newValue; } } private int decreaseAppCount() { final Integer appCounter = (Integer) getHttpSession().getAttribute(SESSION_CMS_APP_COUNT); if (appCounter == null || appCounter.intValue() <= 1) { getHttpSession().removeAttribute(SESSION_CMS_APP_COUNT); return 0; } else { final int newValue = appCounter.intValue() - 1; getHttpSession().setAttribute(SESSION_CMS_APP_COUNT, new Integer(newValue)); return newValue; } } }