Skip to content

Commit

Permalink
KNL-75
Browse files Browse the repository at this point in the history
Patch from Cris Holdorph

As documented in this area of Sakai confluence

http://confluence.sakaiproject.org/confluence/display/TERRA/Home

This is the work that enables Sakai sessions to be clustered using Terracotta. Additional work must still be done for any tool that would like to support clustering. This is also documented in the confluence space above.

Thanks

git-svn-id: https://source.sakaiproject.org/svn/kernel/trunk@54470 66ffb92e-73f9-0310-93c1-f5514f145a0a
  • Loading branch information
ieb committed Nov 18, 2008
1 parent 95d150d commit 9eb89bd
Show file tree
Hide file tree
Showing 31 changed files with 4,870 additions and 1,322 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ public interface UsageSession extends Comparable
* @return the server id which is hosting this session.
*/
String getServer();

/**
* Set the server id which is hosting this session.
*
* @param serverId the server id which is hosting this session.
*/
public void setServer(String serverId);

/**
* Access the user id for this session.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**********************************************************************************
*
* Copyright (c) 2008 The Sakai Foundation.
*
* Licensed under the Educational Community License, Version 1.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.opensource.org/licenses/ecl1.php
*
* 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.sakaiproject.tool.api;

import java.util.Map;

/**
* NonPortableSession stores a users session data that can not be 'shared'
* in a Terracotta (or similar) cluster.
*/
public interface NonPortableSession {

/**
* Returns the object bound with the specified name in this session, or <code>null</code> if no object is bound under the name.
*
* @param name
* a string specifying the name of the object
* @return the object with the specified name
* @exception IllegalStateException
* if this method is called on an invalidated session
*/
public Object getAttribute(String name);

/**
* Removes the object bound with the specified name from this session. If the session does not have an object bound with the specified name, this method does nothing.
* <p>
* After this method executes, and if the object implements <code>SessionBindingListener</code>, Sakai calls <code>SessionBindingListener.valueUnbound</code>.
*
* @param name
* the name of the object to remove from this session
* @return the attribute with the specified name if it existed
* @exception IllegalStateException
* if this method is called on an invalidated session
*/
public Object removeAttribute(String name);

/**
* Binds an object to this session, using the name specified. If an object of the same name is already bound to the session, the object is replaced.
* <p>
* After this method executes, and if the new object implements <code>SessionBindingListener</code>, Sakai calls <code>SessionBindingListener.valueBound</code>.
* <p>
* If an object was already bound to this session of this name that implements <code>SessionBindingListener</code>, its <code>SessionBindingListener.valueUnbound</code> method is called.
* <p>
* If the value passed in is null, this has the same effect as calling <code>removeAttribute()<code>.
*
* @param name the name to which the object is bound;
* cannot be null
*
* @param value the object to be bound
* @return the old attribute with the specified name if it existed
* @exception IllegalStateException if this method is called on an
* invalidated session
*/
public Object setAttribute(String name, Object value);

/**
* Get all the attributes in this Session. The returned data structure is a copy of all the attributes in this Session and does not
* represent the backing data structure of the Session itself.
* @return a new Map object representing the key/value pair of all attributes in this session
*/
public Map<String,Object> getAllAttributes();

/**
* Remove all attributes from this session
*/
public void clear();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.sakaiproject.tool.api;

public interface SessionAttributeListener {

public void attributeAdded(SessionBindingEvent se);
public void attributeRemoved(SessionBindingEvent se);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**********************************************************************************
* $URL$
* $Id$
***********************************************************************************
*
* Copyright (c) 2008 The Sakai Foundation.
*
* Licensed under the Educational Community License, Version 1.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.opensource.org/licenses/ecl1.php
*
* 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.sakaiproject.tool.api;

/**
* SessionStore it a mix-in interface for use most commonly with SessionManager.
* This interface is to represent the ability to manipulate the underlying storage
* of Sessions that the SessionManager is managing.
*
* @author holdorph
*/
public interface SessionStore {

/**
* Remove the Session corresponding to this id from the
* Session storage.
*
* @param id the session identifier
*/
public void remove(String id);

/**
* Checks the current Tool ID to determine if this tool is marked for clustering.
*
* @return true if the tool is marked for clustering, false otherwise.
*/
public boolean isCurrentToolClusterable();
}
47 changes: 41 additions & 6 deletions kernel/api/src/main/java/org/sakaiproject/util/RequestFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.component.api.ServerConfigurationService;
import org.sakaiproject.event.api.UsageSession;
import org.sakaiproject.event.api.UsageSessionService;
import org.sakaiproject.thread_local.cover.ThreadLocalManager;
import org.sakaiproject.tool.api.Session;
import org.sakaiproject.tool.api.Tool;
Expand Down Expand Up @@ -235,6 +237,9 @@ public class RequestFilter implements Filter

/** The servlet context for the filter. */
protected ServletContext m_servletContext = null;

/** Is this a Terracotta clustered environment? */
protected boolean TERRACOTTA_CLUSTER = false;

/**
* Wraps a request object so we can override some standard behavior.
Expand Down Expand Up @@ -588,11 +593,13 @@ public void doFilter(ServletRequest requestObj, ServletResponse responseObj, Fil
ThreadLocalManager.set(ServerConfigurationService.CURRENT_PORTAL_PATH, "/" + m_contextId);
}

// Pass control on to the next filter or the servlet
chain.doFilter(req, resp);
synchronized(s) {
// Pass control on to the next filter or the servlet
chain.doFilter(req, resp);

// post-process response
postProcessResponse(s, req, resp);
// post-process response
postProcessResponse(s, req, resp);
}
}
catch (Throwable t)
{
Expand Down Expand Up @@ -771,6 +778,9 @@ else if ("tool".equalsIgnoreCase(s))
M_log.warn("overridding " + CONFIG_MAX_PER_FILE + " setting: must be 'true' with " + CONFIG_CONTINUE + " ='true'");
m_uploadMaxPerFile = true;
}

String clusterTerracotta = System.getProperty("sakai.cluster.terracotta");
TERRACOTTA_CLUSTER = "true".equals(clusterTerracotta);
}

/**
Expand Down Expand Up @@ -1073,7 +1083,9 @@ protected Session assureSession(HttpServletRequest req, HttpServletResponse res)
// if found and not automatic, mark it as active
if ((s != null) && (!auto))
{
s.setActive();
synchronized(s) {
s.setActive();
}
}

// if missing, make one
Expand All @@ -1093,6 +1105,25 @@ protected Session assureSession(HttpServletRequest req, HttpServletResponse res)

// set this as the current session
SessionManager.setCurrentSession(s);

// Now that we know the session exists, regardless of whether it's new or not, lets see if there
// is a UsageSession. If so, we want to check it's serverId
UsageSession us = null;
synchronized(s) {
us = (UsageSession)s.getAttribute(UsageSessionService.USAGE_SESSION_KEY);
if (us != null) {
// check the server instance id
ServerConfigurationService configService = org.sakaiproject.component.cover.ServerConfigurationService.getInstance();
String serverInstanceId = configService.getServerIdInstance();
if ((serverInstanceId != null) && (!serverInstanceId.equals(us.getServer()))) {
// Log that the UsageSession server value is changing
M_log.info("UsageSession: Server change detected: Old Server=" + us.getServer() +
" New Server=" + serverInstanceId);
// set the new UsageSession server value
us.setServer(serverInstanceId);
}
}
}

// if we had a cookie and we have no session, clear the cookie TODO: detect closed session in the request
if ((s == null) && (c != null))
Expand Down Expand Up @@ -1225,7 +1256,11 @@ protected Cookie findCookie(HttpServletRequest req, String name, String suffix)
{
if (cookies[i].getName().equals(name))
{
if ((suffix == null) || cookies[i].getValue().endsWith(suffix))
// If this is NOT a terracotta cluster environment
// and the suffix passed in to this method is not null
// then only match the cookie if the end of the cookie
// value is equal to the suffix passed in.
if (TERRACOTTA_CLUSTER || ((suffix == null) || cookies[i].getValue().endsWith(suffix)))
{
return cookies[i];
}
Expand Down
20 changes: 20 additions & 0 deletions kernel/component-manager/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,26 @@
<properties>
<deploy.target>shared</deploy.target>
</properties>
<profiles>
<profile>
<id>sakai-sun</id>
<activation>
<property>
<name>java.vendor</name>
<value>Sun Microsystems Inc.</value>
</property>
</activation>
<dependencies>
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.5.0</version>
<scope>system</scope>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>
</dependencies>
</profile>
</profiles>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import java.io.File;
import java.io.FileFilter;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
Expand Down Expand Up @@ -272,9 +273,19 @@ public boolean accept(File file)

// make the array from the list
URL[] urlArray = (URL[]) urls.toArray(new URL[urls.size()]);
ClassLoader loader = null;

// make the classloader - my loader is parent
URLClassLoader loader = new URLClassLoader(urlArray, getClass().getClassLoader());
// Check to see if Terracotta clustering is turned on
// String clusterTerracotta = ServerConfigurationService.getString("cluster.terracotta","false");
String clusterTerracotta = System.getProperty("sakai.cluster.terracotta");

if ("true".equals(clusterTerracotta)) {
// If Terracotta clustering is turned on then use the Special Terracotta Class loader
loader = new TerracottaClassLoader(urlArray, getClass().getClassLoader(), dir.getName());
} else {
// Terracotta clustering is turned off, so use the normal URLClassLoader
loader = new URLClassLoader(urlArray, getClass().getClassLoader());
}

return loader;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package org.sakaiproject.util;

import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

class TerracottaClassLoader extends URLClassLoader {
/** Our logger */
private static Log log = LogFactory.getLog(ComponentsLoader.class);

private String classLoaderName;

public TerracottaClassLoader(URL[] urls, ClassLoader parent, String classLoaderName) {
super(urls, parent);

this.classLoaderName = classLoaderName;

boolean registeredWithTerracotta = false;

try {
if (parent == null) {
log.error("Parent classloader is set to null.");
} else {
Class<?> namedClassLoader = parent.loadClass("com.tc.object.loaders.NamedClassLoader");
if (namedClassLoader == null) {
log.error("Could not load Terracotta NamedClassLoader");
} else {
Class<?> helper = parent.loadClass("com.tc.object.bytecode.hook.impl.ClassProcessorHelper");
if (helper == null) {
log.error("Could not load Terracotta ClassProcessorHelper");
} else {
Method m = helper.getMethod("registerGlobalLoader", new Class<?>[] { namedClassLoader });
if (m == null) {
log.error("Could not find Terracotta Method - \"registerGlobalLoader\"");
} else {
m.invoke(null, new Object[] { this });
registeredWithTerracotta = true;
if (log.isInfoEnabled()) {
log.info("Registered the [" + classLoaderName +
"] class loader with Terracotta.");
}
}
}
}
}
} catch (Throwable t) {
// It is important that normal startup not suffer if Terracotta is not running
// so catch any error that occurs in this block of code dealing with Terracotta
// class loader registering
log.error("Unexpected error occurred trying to register class loader [" +
classLoaderName + "] with Terracotta",t);
}
if (!registeredWithTerracotta) {
log.warn("The [" + classLoaderName +
"] class loader is not registered with Terracotta. Objects from this " +
"class loader will not be shared.");
}
}

// needed for Terracotta Clustering to work. Harmless for non-Terracotta environments
public String __tc_getClassLoaderName() {
return classLoaderName;
}
}
Loading

0 comments on commit 9eb89bd

Please sign in to comment.