/*
* Copyright 2005 Joe Walker
*
* 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.directwebremoting.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.directwebremoting.ConversionException;
import org.directwebremoting.ScriptBuffer;
import org.directwebremoting.event.ScriptSessionBindingEvent;
import org.directwebremoting.event.ScriptSessionBindingListener;
import org.directwebremoting.extend.RealScriptSession;
import org.directwebremoting.extend.ScriptConduit;
import org.directwebremoting.util.IdGenerator;
/**
* An implementation of ScriptSession and RealScriptSession.
* <p>There are synchronization constraints on this class. See the field
* comments of the type: <code>GuardedBy("lock")</code>.
* <p>In addition you should note that {@link TransientScriptSession} and
* {@link TransientScriptSessionManager} make calls to each other and you should
* take care not to break any constraints in inheriting from these classes.
* @author Joe Walker [joe at getahead dot ltd dot uk]
*/
public class TransientScriptSession implements RealScriptSession
{
/**
* Simple constructor
* @param manager The manager that created us
* @param page The URL of the page on which we sit
*/
protected TransientScriptSession(TransientScriptSessionManager manager, String page)
{
this.page = page;
this.manager = manager;
}
/* (non-Javadoc)
* @see org.directwebremoting.ScriptSession#getAttribute(java.lang.String)
*/
public Object getAttribute(String name)
{
synchronized (attributes)
{
return attributes.get(name);
}
}
/* (non-Javadoc)
* @see org.directwebremoting.ScriptSession#setAttribute(java.lang.String, java.lang.Object)
*/
public void setAttribute(String name, Object value)
{
synchronized (attributes)
{
if (value != null)
{
if (value instanceof ScriptSessionBindingListener)
{
ScriptSessionBindingListener listener = (ScriptSessionBindingListener) value;
listener.valueBound(new ScriptSessionBindingEvent(this, name));
}
attributes.put(name, value);
}
else
{
removeAttribute(name);
}
}
}
/* (non-Javadoc)
* @see org.directwebremoting.ScriptSession#removeAttribute(java.lang.String)
*/
public void removeAttribute(String name)
{
synchronized (attributes)
{
Object value = attributes.remove(name);
if (value != null && value instanceof ScriptSessionBindingListener)
{
ScriptSessionBindingListener listener = (ScriptSessionBindingListener) value;
listener.valueUnbound(new ScriptSessionBindingEvent(this, name));
}
}
}
/* (non-Javadoc)
* @see org.directwebremoting.ScriptSession#getAttributeNames()
*/
public Iterator<String> getAttributeNames()
{
synchronized (attributes)
{
Set<String> keys = Collections.unmodifiableSet(attributes.keySet());
return keys.iterator();
}
}
/* (non-Javadoc)
* @see org.directwebremoting.ScriptSession#invalidate()
*/
public void invalidate()
{
throw new IllegalStateException("TransientScriptSession should not be invalidated");
}
/* (non-Javadoc)
* @see org.directwebremoting.ScriptSession#isInvalidated()
*/
public boolean isInvalidated()
{
return false;
}
/* (non-Javadoc)
* @see org.directwebremoting.ScriptSession#getId()
*/
public String getId()
{
return scriptSessionId;
}
/* (non-Javadoc)
* @see org.directwebremoting.ScriptSession#getCreationTime()
*/
public long getCreationTime()
{
return System.currentTimeMillis();
}
/* (non-Javadoc)
* @see org.directwebremoting.ScriptSession#getLastAccessedTime()
*/
public long getLastAccessedTime()
{
return System.currentTimeMillis();
}
/* (non-Javadoc)
* @see org.directwebremoting.ScriptSession#addScript(java.lang.String)
*/
public void addScript(ScriptBuffer script)
{
if (script == null)
{
throw new NullPointerException("null script");
}
// First we try to add the script to an existing conduit
synchronized (scriptLock)
{
if (conduits.isEmpty())
{
boolean written = false;
// This would be an excellent solution to the connection limit
// problem, however browsers are extending their limits, and
// we don't have inter-window communication sorted yet.
// If we do sort out I-W comms, then uncomment this, add a
// member: private String httpSessionId;
// which is passed into the constructor from the Manager
/*
// Are there any other script sessions in the same browser
// that could proxy the script for us?
Collection<RealScriptSession> sessions = manager.getScriptSessionsByHttpSessionId(httpSessionId);
ScriptBuffer proxyScript = EnginePrivate.createForeignWindowProxy(getWindowName(), script);
for (Iterator<RealScriptSession> it = sessions.iterator(); !written && it.hasNext();)
{
RealScriptSession session = it.next();
written = session.addScriptImmediately(proxyScript);
}
*/
if (!written)
{
scripts.add(script);
}
}
else
{
// Try all the conduits, starting with the first
boolean written = false;
for (Iterator<ScriptConduit> it = conduits.iterator(); !written && it.hasNext();)
{
ScriptConduit conduit = it.next();
try
{
written = conduit.addScript(script);
}
catch (Exception ex)
{
it.remove();
log.debug("Failed to write to ScriptConduit, removing conduit from list: " + conduit);
}
}
if (!written)
{
scripts.add(script);
}
}
}
}
/* (non-Javadoc)
* @see org.directwebremoting.extend.RealScriptSession#addScriptImmediately(org.directwebremoting.ScriptBuffer)
*/
public boolean addScriptImmediately(ScriptBuffer script)
{
return false;
}
/* (non-Javadoc)
* @see org.directwebremoting.extend.RealScriptSession#countPersistentConnections()
*/
public int countPersistentConnections()
{
int persistentConnections = 0;
for (ScriptConduit conduit : conduits)
{
if (conduit.isHoldingConnectionToBrowser())
{
persistentConnections++;
}
}
return persistentConnections;
}
/* (non-Javadoc)
* @see org.directwebremoting.extend.RealScriptSession#addScriptConduit(org.directwebremoting.extend.ScriptConduit)
*/
public void addScriptConduit(ScriptConduit conduit) throws IOException
{
synchronized (scriptLock)
{
writeScripts(conduit);
conduits.add(conduit);
}
}
/* (non-Javadoc)
* @see org.directwebremoting.extend.RealScriptSession#writeScripts(org.directwebremoting.extend.ScriptConduit)
*/
public void writeScripts(ScriptConduit conduit) throws IOException
{
synchronized (scriptLock)
{
for (Iterator<ScriptBuffer> it = scripts.iterator(); it.hasNext();)
{
ScriptBuffer script = it.next();
try
{
if (conduit.addScript(script))
{
it.remove();
}
else
{
// If we didn't write this one, don't bother with any more
break;
}
}
catch (ConversionException ex)
{
log.error("Failed to convert data. Dropping Javascript: " + script, ex);
it.remove();
}
}
}
}
/* (non-Javadoc)
* @see org.directwebremoting.extend.RealScriptSession#removeScriptConduit(org.directwebremoting.extend.ScriptConduit)
*/
public void removeScriptConduit(ScriptConduit conduit)
{
synchronized (scriptLock)
{
boolean removed = conduits.remove(conduit);
if (!removed)
{
log.debug("removeScriptConduit called with ScriptConduit not in our list. conduit=" + conduit);
debug();
}
}
}
/* (non-Javadoc)
* @see org.directwebremoting.extend.RealScriptSession#hasWaitingScripts()
*/
public boolean hasWaitingScripts()
{
synchronized (scriptLock)
{
return !scripts.isEmpty();
}
}
/* (non-Javadoc)
* @see org.directwebremoting.ScriptSession#getPage()
*/
public String getPage()
{
return page;
}
/* (non-Javadoc)
* @see org.directwebremoting.extend.RealScriptSession#setWindowName(java.lang.String)
*/
public void setWindowName(String windowName)
{
this.windowName = windowName;
}
/* (non-Javadoc)
* @see org.directwebremoting.extend.RealScriptSession#getWindowName()
*/
public String getWindowName()
{
return windowName;
}
/* (non-Javadoc)
* @see org.directwebremoting.extend.RealScriptSession#updateLastAccessedTime()
*/
public void updateLastAccessedTime()
{
}
/**
* Some debug output
*/
private void debug()
{
if (log.isDebugEnabled())
{
log.debug("Found " + conduits.size() + " ScriptConduits attached to " + this);
for (ScriptConduit scriptConduit : conduits)
{
log.debug("- " + scriptConduit);
}
}
}
/**
* How we create script session ids.
*/
private static IdGenerator generator = new IdGenerator();
/**
* The server side attributes for this page.
* <p>GuardedBy("attributes")
*/
protected final Map<String, Object> attributes = Collections.synchronizedMap(new HashMap<String, Object>());
/**
* The script conduits that we can use to transfer data to the browser.
* <p>GuardedBy("scriptLock")
*/
protected final SortedSet<ScriptConduit> conduits = new TreeSet<ScriptConduit>();
/**
* The list of waiting scripts.
* <p>GuardedBy("scriptLock")
*/
protected final List<ScriptBuffer> scripts = new ArrayList<ScriptBuffer>();
/**
* The object that we use to synchronize against when we want to alter
* the path of scripts (to conduits or the scripts list)
*/
private final Object scriptLock = new Object();
/**
* The page being viewed.
*/
protected final String page;
/**
* ScriptSessionIds for {@link TransientScriptSession}s are very temporary
*/
protected final String scriptSessionId = generator.generateId(16);
/**
* We track window names to link script sessions together and to help
* foil the 2 connection limit
*/
private String windowName;
/**
* The session manager that collects sessions together
* <p>This should not need careful synchronization since it is unchanging
*/
protected final TransientScriptSessionManager manager;
/**
* The log stream
*/
private static final Log log = LogFactory.getLog(TransientScriptSession.class);
}