/**
* Licensed to Jasig under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Jasig 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:
*
* 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.portal.jndi;
import java.util.Enumeration;
import javax.naming.CompositeName;
import javax.naming.Context;
import javax.naming.NameAlreadyBoundException;
import javax.naming.NameClassPair;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.servlet.http.HttpSession;
import org.jasig.portal.PortalException;
import org.jasig.portal.spring.web.context.support.HttpSessionDestroyedEvent;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.jndi.JndiAccessor;
import org.springframework.jndi.JndiCallback;
import org.springframework.jndi.JndiTemplate;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class JndiManagerImpl extends JndiAccessor implements IJndiManager, ApplicationListener, InitializingBean {
/* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception {
this.initializePortalContext();
}
/* (non-Javadoc)
* @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent)
*/
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof HttpSessionDestroyedEvent) {
final HttpSession session = ((HttpSessionDestroyedEvent)event).getSession();
this.destroySessionContext(session);
}
}
/* (non-Javadoc)
* @see org.jasig.portal.jndi.IJndiManager#initializeSessionContext(javax.servlet.http.HttpSession, java.lang.String, java.lang.String, org.w3c.dom.Document)
*/
public void initializeSessionContext(HttpSession session, String userId, String layoutId, Document userLayout) {
final String sessionId = session.getId();
final JndiTemplate jndiTemplate = this.getJndiTemplate();
// bind userId to /sessions context
try {
final Context sessionsContext = (Context) jndiTemplate.lookup("/sessions", Context.class);
try {
sessionsContext.bind(sessionId, userId);
}
catch (NameAlreadyBoundException nabe) {
sessionsContext.rebind(sessionId, userId);
}
}
catch (NamingException ne) {
this.logger.warn("Unable to obtain /sessions context, no session data will be available in the context for sessionId='" + sessionId + "', userId='" + userId + "', and layoutId='" + layoutId + "'", ne);
}
final Context usersContext;
try {
// get /users context
usersContext = (Context) jndiTemplate.lookup("/users", Context.class);
}
catch (NamingException ne) {
final PortalException portalException = new PortalException("Could not find /users context", ne);
this.logger.error(portalException.getMessage(), ne);
throw portalException;
}
// get or create /users/[userId] context
Context userIdContext = null;
Context sessionsContext = null;
Context layoutsContext = null;
try {
userIdContext = (Context) usersContext.lookup(userId);
// lookup layouts and sessions contexts
try {
layoutsContext = (Context) userIdContext.lookup("layouts");
}
catch (NamingException ne) {
this.logger.warn("The '/users/" + userId + "/layouts' Context did not exist, even though the '/users/" + userId + "' Context did. It will be created.");
layoutsContext = userIdContext.createSubcontext("layouts");
}
try {
sessionsContext = (Context) userIdContext.lookup("sessions");
}
catch (NamingException ne) {
this.logger.error("The Context '/users/" + userId + "/sessions' did not exist, even though the '/users/" + userId + "' Context did. It will be created.");
sessionsContext = userIdContext.createSubcontext("sessions");
}
}
catch (NamingException ne) {
// new user
try {
userIdContext = usersContext.createSubcontext(userId);
// create layouts and sessions context
layoutsContext = userIdContext.createSubcontext("layouts");
sessionsContext = userIdContext.createSubcontext("sessions");
if (this.logger.isDebugEnabled()) {
this.logger.debug("Created and initialized Contexts for a userId='" + userId + "'");
}
}
catch (NamingException ne2) {
final PortalException portalException = new PortalException("exception encountered while trying to create '/users/" + userId + "' and layouts/sessions Contexts", ne2);
this.logger.error(portalException.getMessage(), ne2);
throw portalException;
}
}
// bind sessions/[sessionId] context
final Context sessionIdContext;
try {
sessionIdContext = sessionsContext.createSubcontext(sessionId);
}
catch (NameAlreadyBoundException nabe) {
final PortalException portalException = new PortalException("A session context is already bound at '/users/" + userId + "/sessions/" + sessionId + "'", nabe);
this.logger.error(portalException.getMessage(), nabe);
throw portalException;
}
catch (NamingException ne) {
final PortalException portalException = new PortalException("Excpetion encountered while trying to create Context '/users/" + userId + "/sessions/" + sessionId + "'", ne);
this.logger.error(portalException.getMessage(), ne);
throw portalException;
}
// bind layoutId
try {
sessionIdContext.bind("layoutId", layoutId);
}
catch (NamingException ne) {
final PortalException portalException = new PortalException("Excpetion encountered while trying to bind '" + layoutId + "' to '/users/" + userId + "/sessions/" + sessionId + "/layoutId'", ne);
this.logger.error(portalException.getMessage(), ne);
throw portalException;
}
// make sure channel-obj context exists
try {
sessionIdContext.createSubcontext("channel-obj");
}
catch (NameAlreadyBoundException nabe) {
// ignore
}
catch (NamingException ne) {
this.logger.warn("Excpetion encountered while create Context '" + layoutId + "' to '/users/" + userId + "/sessions/" + sessionId + "/channel-obj', this will be ignored.", ne);
}
// check if the layout id binding already exists
try {
//Check if layouts/[layoutId]/ alread exists
layoutsContext.lookup(layoutId);
// assume layouts/[layoutId]/ has already been populated
// bind layouts/[layoutId]/sessions/[sessionId]
final Context layoutSessionsContext;
try {
layoutSessionsContext = (Context) userIdContext.lookup("layouts/" + layoutId + "/sessions");
}
catch (NamingException ne) {
final PortalException portalException = new PortalException("Exception occured while looking up Context '/users/" + userId + "/layouts/" + layoutId + "/sessions/' even though Context '/users/" + userId + "/layouts' already existed.", ne);
this.logger.error(portalException.getMessage(), ne);
throw portalException;
}
try {
layoutSessionsContext.createSubcontext(sessionId);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Created Context '/users/" + userId + "/layouts/" + layoutId + "/sessions/" + sessionId + "'");
}
}
catch (NamingException ne) {
final PortalException portalException = new PortalException("Exception occured while creating Context '/users/" + userId + "/layouts/" + layoutId + "/sessions/" + sessionId + "'", ne);
this.logger.error(portalException.getMessage(), ne);
throw portalException;
}
}
catch (NamingException ne) {
final Context layoutIdContext;
try {
// given layout id has not been registered yet
layoutIdContext = layoutsContext.createSubcontext(layoutId);
// bind layouts/[layoutId]/sessions/[sessionId] context
final Context layoutSessionsContext = layoutIdContext.createSubcontext("sessions");
layoutSessionsContext.createSubcontext(sessionId);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Created Context '/users/" + userId + "/layouts/" + layoutId + "'");
}
}
catch (NamingException ne2) {
final PortalException portalException = new PortalException("Exception occured while creating the '/users/" + userId + "/layouts/" + layoutId + "' Context.", ne2);
this.logger.error(portalException.getMessage(), ne2);
throw portalException;
}
try {
final Context channel_idsContext = layoutIdContext.createSubcontext("channel-ids");
// Get the list of channels in the user's layout
final NodeList channelNodes = userLayout.getElementsByTagName("channel");
// Parse through the channels and populate the JNDI
for (int channelNodeIndex = 0; channelNodeIndex < channelNodes.getLength(); channelNodeIndex++) {
// Attempt to get the fname and instance ID from the channel
final Node channelNode = channelNodes.item(channelNodeIndex);
final NamedNodeMap channelAttributes = channelNode.getAttributes();
final Node fname = channelAttributes.getNamedItem("fname");
final Node instanceId = channelAttributes.getNamedItem("ID");
if (fname != null && instanceId != null) {
//System.out.println("fname found -> " + fname);
// Create a new composite name from the fname
final CompositeName cname = new CompositeName(fname.getNodeValue());
// Get a list of the name components
final Enumeration<String> subContextNameEnum = cname.getAll();
// Get the root of the context
Context nextContext = channel_idsContext;
// Add all of the subcontexts in the fname
while (subContextNameEnum.hasMoreElements()) {
final String subContextName = subContextNameEnum.nextElement();
if (subContextNameEnum.hasMoreElements()) {
// Bind a new sub context if the current name component is not the leaf
nextContext = nextContext.createSubcontext(subContextName);
}
else {
nextContext.rebind(subContextName, instanceId.getNodeValue());
if (this.logger.isDebugEnabled()) {
this.logger.debug("Bound channel id '" + instanceId.getNodeValue() + "' to '" + nextContext.getNameInNamespace() + "/" + subContextName + "'");
}
}
}
}
}
}
catch (NamingException ne2) {
final PortalException portalException = new PortalException("Exception occured while creating or populating the '/users/" + userId + "/layouts/" + layoutId + "/channel-ids' Context.", ne2);
this.logger.error(portalException.getMessage(), ne2);
throw portalException;
}
}
this.logger.info("JNDI Context configured for sessionId='" + sessionId + "', userId='" + userId + "', and layoutId='" + layoutId + "'");
}
/* (non-Javadoc)
* @see org.jasig.portal.jndi.IJndiManager#destroySessionContext(javax.servlet.http.HttpSession)
*/
public void destroySessionContext(HttpSession session) {
final JndiTemplate jndiTemplate = this.getJndiTemplate();
final String sessionId = session.getId();
final Context usersContext;
try {
// get /users context
usersContext = (Context) jndiTemplate.lookup("/users", Context.class);
}
catch (NamingException ne) {
final PortalException portalException = new PortalException("Could not find /users context", ne);
this.logger.error(portalException.getMessage(), ne);
throw portalException;
}
//No context, nothing to do
if (usersContext == null) {
this.logger.warn("No JNDI Context removed for sessionId='" + sessionId + "'");
return;
}
// obtain /sessions context
final Context topSessionsContext;
try {
topSessionsContext = (Context) jndiTemplate.lookup("/sessions", Context.class);
}
catch (NamingException ne) {
this.logger.warn("Could not get /sessions context. No JNDI context will be removed for sessionId='" + sessionId + "'", ne);
return;
}
final String userId;
// obtain userId by looking at /sessions bindings
try {
userId = (String) topSessionsContext.lookup(sessionId);
}
catch (NamingException ne) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Session '" + sessionId + "' is not registered under /sessions context, returning immediatly.", ne);
}
return;
}
if (userId == null) {
// could do a /users/[userId]/sessions/* traversal here instead
this.logger.warn("Unable to determine userId for a session " + sessionId + " ... giving up on JNDI cleanup.");
return;
}
// unbind userId binding in /sessions
try {
topSessionsContext.unbind(sessionId);
}
catch (NamingException ne) {
this.logger.warn("Problems unbinding '/sessions/" + sessionId + "', continuing with cleanup.", ne);
}
final Context userIdContext;
try {
userIdContext = (Context) usersContext.lookup(userId);
}
catch (NamingException ne) {
this.logger.warn("Context '/users/" + userId + "' doesn't exist. Ending JNDI Cleanup here.", ne);
return;
}
final Context sessionsContext;
try {
sessionsContext = (Context) userIdContext.lookup("sessions");
}
catch (NamingException ne) {
this.logger.warn("Context '/users/" + userId + "/sessions' doesn't exist. Ending JNDI Cleanup here.", ne);
return;
}
final Context sessionIdContext;
try {
sessionIdContext = (Context) sessionsContext.lookup(sessionId);
}
catch (NamingException ne) {
this.logger.warn("Context '/users/" + userId + "/sessions/" + sessionId + "' doesn't exist. Ending JNDI Cleanup here.", ne);
return;
}
// determine layoutId
String layoutId = null;
try {
layoutId = (String) sessionIdContext.lookup("layoutId");
}
catch (NamingException ne) {
this.logger.warn("'/users/" + userId + "/sessions/" + sessionId + "/layoutId' is not bound.", ne);
}
// destroy sessionIdContext
try {
sessionsContext.unbind(sessionId);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Destroyed Context '/users/" + userId + "/sessions/" + sessionId + "'");
}
}
catch (NamingException ne) {
this.logger.warn("Exception occurred while trying to destroy context '/users/" + userId + "/sessions/" + sessionId + "', ignoring and continuing with cleanup.", ne);
}
// see if this was the only session
try {
final NamingEnumeration<NameClassPair> userSessionsList = userIdContext.list("sessions");
if (!userSessionsList.hasMore()) {
// destroy userIdContext alltogether
usersContext.unbind(userId);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Destroyed Context '/users/" + userId + "' since the last remaining session has been unbound.");
}
}
else {
// remove sessionId from the layouts/[layoutId]/sessions
try {
final Context layoutsContext = (Context) userIdContext.lookup("layouts");
try {
final Context layoutIdContext = (Context) layoutsContext.lookup(layoutId);
try {
final Context layoutSessionsContext = (Context) layoutIdContext.lookup("sessions");
// unbind sessionId
layoutSessionsContext.unbind(sessionId);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Destroyed Context '/users/" + userId + "/layouts/" + layoutId + "/sessions/" + sessionId + "'");
}
// see if the lsessionsContext is empty
final NamingEnumeration<NameClassPair> layoutSessionsList = layoutIdContext.list("sessions");
if (!layoutSessionsList.hasMore()) {
// destroy the layoutId context
try {
layoutsContext.unbind(layoutId);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Destroyed Context '/users/" + userId + "/layouts/" + layoutId + "' since the last session using it has been unbound.");
}
}
catch (NamingException ne) {
this.logger.warn("Exception while destroying '/users/" + userId + "/layouts/" + layoutId + "', ignoring and continuing with cleanup.", ne);
}
}
}
catch (NamingException ne) {
this.logger.warn("Exception while looking up '/users/" + userId + "/layouts/" + layoutId + "/sesions', ignoring and continuing with cleanup.", ne);
}
}
catch (NamingException ne) {
this.logger.warn("Exception while looking up /users/" + userId + "/layouts/', ignoring and continuing with cleanup.", ne);
}
}
catch (NamingException ne) {
this.logger.warn("Exception while looking up /users/" + userId + "/layouts', ignoring and continuing with cleanup.", ne);
}
}
}
catch (NamingException ne) {
this.logger.warn("Error listing '/users/" + userId + "/sessions/', ignoring and continuing with cleanup.", ne);
}
this.logger.info("JNDI Context removed for sessionId='" + sessionId + "', userId='" + userId + "', and layoutId='" + layoutId + "'");
}
/**
* Sets up the base sub contexts in the portal JNDI context.
*/
protected void initializePortalContext() throws NamingException {
final JndiTemplate jndiTemplate = this.getJndiTemplate();
// Create a subcontext for portal-wide services, initialize services
// Start any portal services configured in services.xml
jndiTemplate.execute(new CreateSubContextCallback("services"));
// Create a subcontext for user specific bindings
jndiTemplate.execute(new CreateSubContextCallback("users"));
// Create a subcontext for session listings
jndiTemplate.execute(new CreateSubContextCallback("sessions"));
this.logger.info("Initialized portal JNDI context");
}
/**
* Creates a sub-context with the specified name.
*/
private static class CreateSubContextCallback implements JndiCallback {
private final String subContextName;
public CreateSubContextCallback(String subContextName) {
this.subContextName = subContextName;
}
/* (non-Javadoc)
* @see org.springframework.jndi.JndiCallback#doInContext(javax.naming.Context)
*/
public Object doInContext(Context ctx) throws NamingException {
final Context subcontext = ctx.createSubcontext(this.subContextName);
return subcontext;
}
}
}