User: jules_gosnell
Date: 02/01/13 05:26:45
Added: jetty/src/main/org/jboss/jetty/session
AbstractHttpSessionData.java
CoarseDistributedStore.java
DistributedHttpSession.java
DistributedHttpSessionManager.java
Log:
split src into dirs
check in latest DistributedSession code
Revision Changes Path
1.1
contrib/jetty/src/main/org/jboss/jetty/session/AbstractHttpSessionData.java
Index: AbstractHttpSessionData.java
===================================================================
/*
* jBoss, the OpenSource EJB server
*
* Distributable under GPL license.
* See terms of license at gnu.org.
*/
// $Id: AbstractHttpSessionData.java,v 1.1 2002/01/13 13:26:45 jules_gosnell Exp $
//----------------------------------------
package org.jboss.jetty.session;
//----------------------------------------
import java.util.Map;
//----------------------------------------
/**
* An abstraction of the data used for the distributed store of HttpSessions
*
* @author <a href="mailto:jules_gosnell@@yahoo.com">Jules Gosnell</a>
* @version 1.0
* @since 1.0
*/
public interface
AbstractHttpSessionData
{
// from javax.servlet.http.HttpSession
public long getCreationTime();
public String getId();
public long getLastAccessedTime();
public int getMaxInactiveInterval();
public void setMaxInactiveInterval(int maxInactiveInterval);
// extra accessors
public Map getAttributes();
public void setAttributes(Map attributes);
public void setId(String id);
public void setCreationTime(long creationTime);
public void setLastAccessedTime(long lastAccessedTime);
// extra attributes
public boolean getAttributesWerePassivated();
public void setAttributesWerePassivated(boolean attributesWerePassivated);
}
1.1
contrib/jetty/src/main/org/jboss/jetty/session/CoarseDistributedStore.java
Index: CoarseDistributedStore.java
===================================================================
/*
* jBoss, the OpenSource EJB server
*
* Distributable under GPL license.
* See terms of license at gnu.org.
*/
// $Id: CoarseDistributedStore.java,v 1.1 2002/01/13 13:26:45 jules_gosnell Exp $
//----------------------------------------
package org.jboss.jetty.session;
//----------------------------------------
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.FinderException;
import javax.ejb.RemoveException;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.rmi.PortableRemoteObject;
import org.jboss.jetty.interfaces.CoarseHttpSession;
import org.jboss.jetty.interfaces.CoarseHttpSessionData;
import org.jboss.jetty.interfaces.CoarseHttpSessionHome;
import org.jboss.logging.Logger;
//----------------------------------------
/**
* An abstraction of a manager for the distributed store of HttpSessions
*
* @author <a href="mailto:jules_gosnell@@yahoo.com">Jules Gosnell</a>
* @version 1.0
* @since 1.0
*/
interface AbstractStore
{
public String nextId();
public AbstractHttpSessionData make();
public AbstractHttpSessionData get(String id);
public void set(String id, AbstractHttpSessionData data);
}
//----------------------------------------
/**
* An implementation of a manager of a CMP based distributed store of HttpSessions
*
* @author <a href="mailto:jules_gosnell@@yahoo.com">Jules Gosnell</a>
* @version 1.0
* @since 1.0
* @see AbstractStore
*/
class EJBDistributedStore
implements AbstractStore
{
Logger _log = Logger.getLogger(getClass().getName());
InitialContext _jndiContext;
CoarseHttpSessionHome _home;
String _name="ejb/jetty/CoarseHttpSession"; // TODO - parameterise
EJBDistributedStore()
{
try
{
_jndiContext=new InitialContext();
Object o=_jndiContext.lookup(_name);
_home=(CoarseHttpSessionHome)PortableRemoteObject.narrow(o,
CoarseHttpSessionHome.class);
_log.info("Support for EJB-based Distributed HttpSessions loaded: "+_home);
}
catch (NamingException e)
{
_log.warn("WARNING: Support for EJB-based Distributed HttpSessions does not
appear to be loaded");
}
}
/**
* create a new HttpSessionData instance, of the correct type for
* this distributed store
*
*/
public AbstractHttpSessionData
make()
{
return new CoarseHttpSessionData();
}
/**
* retrieve HttpSessionData from a distributed store
*
* @param id a <code>String</code> value
*/
public AbstractHttpSessionData
get(String id)
{
AbstractHttpSessionData data=null;
try
{
CoarseHttpSession ejb=_home.findByPrimaryKey(id);
data= ejb.getData();
ejb.remove();
ejb=null;
}
catch (RemoteException e)
{}
catch (FinderException e)
{}
catch (RemoveException e)
{}
catch (Exception e)
{}
return data;
}
/**
* submit HttpSessionData to a distributed store
*
* @param id a <code>String</code> value
* @param data an <code>AbstractHttpSessionData</code> value
*/
public void
set(String id, AbstractHttpSessionData data)
{
try
{
CoarseHttpSession ejb=_home.create((CoarseHttpSessionData)data);
ejb=null;
}
catch (RemoteException e)
{}
catch (CreateException e)
{}
}
final Object _idLock=new Object();
long _nextId=0;
public synchronized String
nextId()
{
long id;
synchronized (_idLock) {
id=_nextId++;
}
return ""+System.currentTimeMillis()+"-"+id;
}
}
1.1
contrib/jetty/src/main/org/jboss/jetty/session/DistributedHttpSession.java
Index: DistributedHttpSession.java
===================================================================
/**
* Implementation of HttpSession with support for a DistributedStore
*
* @author <a href="mailto:jules_gosnell@@yahoo.com">Jules Gosnell</a>
* @version 1.0
* @since 1.0
* @see javax.servlet.http.HttpSession
*/
//----------------------------------------
package org.jboss.jetty.session;
//----------------------------------------
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionContext;
import javax.servlet.http.HttpSessionEvent;
import org.jboss.logging.Logger;
import org.mortbay.jetty.servlet.SessionContext;
//----------------------------------------
interface AbstractHttpSession
extends HttpSession, org.mortbay.jetty.servlet.SessionManager.Session
{
}
class DistributedHttpSession
implements AbstractHttpSession
{
final DistributedHttpSessionManager _manager;
final String _id;
final Logger _log;
final long _creationTime;
final HashMap _attributes;
final boolean _heldByValue =true; // attributes
are held in store by value, not reference
final boolean _accessDirties =true; // updating
_lastAccessedTime dirties session
final boolean _snapshotNeedsActivation =true; // they will
not have been properly passivated
final boolean _isDistributed =true;
volatile boolean _isNew =true; // risky !
volatile boolean _isValid =true; // risky !
volatile boolean _isDirty =false; // needs
storing
final Object _lastAccessedTimeLock =new Object();
long _lastAccessedTime;
final Object _maxInactiveIntervalLock =new Object();
int _maxInactiveInterval;
//----------------------------------------
// create a completely new session
DistributedHttpSession(DistributedHttpSessionManager manager,String id, int
maxInactiveInterval)
{
// final
_manager =manager;
_id =id;
_log =Logger.getLogger(getClass().getName()+"#" +getId());
_creationTime =System.currentTimeMillis();
_attributes =new HashMap(11);
// non-final - no need to lock in ctor
_lastAccessedTime =_creationTime;
_maxInactiveInterval =maxInactiveInterval;
}
// reactivate a passivated session
DistributedHttpSession(DistributedHttpSessionManager manager, String id,
AbstractHttpSessionData data)
{
// assume we own 'data' and don't have to synchronise access
// final
_manager =manager;
_id =id;
_log =Logger.getLogger(getClass().getName()+"#" +getId());
_creationTime =data.getCreationTime();
_attributes =new HashMap(data.getAttributes().size()); // we could just
cast...
// volatile
_isNew =false;
// non-final
_lastAccessedTime =data.getLastAccessedTime(); // no need to lock in ctor
_maxInactiveInterval =data.getMaxInactiveInterval(); // no need to lock in ctor
_attributes.putAll(data.getAttributes());
// send activate events to listening attributes
if (_snapshotNeedsActivation && _attributes.size()>0)
{
boolean warn=!data.getAttributesWerePassivated();
HttpSessionEvent event=new HttpSessionEvent(this);
Iterator i = _attributes.values().iterator();
try
{
while (i.hasNext())
notifySessionDidActivate(i.next(), event, warn);
}
catch (Exception e)
{
_log.error("Problem whilst activating session attributes", e);
}
}
_log.info("Session activated: "+getId());
}
void
destroy()
{
synchronized (_attributes) {
Iterator iter = _attributes.entrySet().iterator(); // guarded
while (iter.hasNext())
{
Entry entry = (Entry)iter.next();
String key = (String)entry.getKey();
Object value = entry.getValue();
iter.remove();
notifyValueUnbound(key, value);
_manager.notifyAttributeRemoved(this, key, value);
}
}
}
//----------------------------------------
// Greg's API - where does he use it ? Should be inheriting it from
// some interface...
public void
access()
{
setLastAccessedTime(System.currentTimeMillis());
}
public void
setLastAccessedTime(long time)
{
synchronized (_lastAccessedTimeLock) {
if (_lastAccessedTime!=time) // guarded
{
_lastAccessedTime=time; // guarded
if (_accessDirties) // final
_isDirty=true; // volatile
}
_isNew=false; // volatile
}
}
//----------------------------------------
// HttpSession API
public String
getId()
{
return _id; // final
}
public long
getCreationTime()
throws IllegalStateException
{
if (!isValid()) throw new IllegalStateException();
return _creationTime; // final
}
public long
getLastAccessedTime()
{
synchronized (_lastAccessedTimeLock) {return _lastAccessedTime;} // guarded
}
public int
getMaxInactiveInterval()
{
synchronized (_maxInactiveIntervalLock){return _maxInactiveInterval;} // guarded
}
public ServletContext
getServletContext()
{
return _manager.getServletContext();
}
/**
* @deprecated
*/
public HttpSessionContext
getSessionContext() // TODO
{
return SessionContext.NULL_IMPL;
}
public boolean
isValid()
{
return _isValid; // volatile
}
public void
setMaxInactiveInterval(int secs)
{
synchronized (_maxInactiveIntervalLock) {
if (_maxInactiveInterval!=secs) // guarded
{
_maxInactiveInterval=secs; // guarded
_isDirty=true; // volatile
}
}
_manager.getScavenger().reregister(this, System.currentTimeMillis(),
(long)secs*1000);
}
public void
invalidate()
throws IllegalStateException
{
_isValid=false;
_manager.destroyHttpSession(this);
}
public boolean
isNew()
throws IllegalStateException
{
if (!isValid()) throw new IllegalStateException();
return _isNew; // volatile
}
// attributes
public Object
getAttribute(String name)
throws IllegalStateException
{
if (!isValid()) throw new IllegalStateException();
synchronized (_attributes) {
return _attributes.get(name);
}
}
public Enumeration
getAttributeNames()
throws IllegalStateException
{
if (!isValid()) throw new IllegalStateException();
// make the Collection whilst synchronised, in case another
// request modifies _attributes whilst we are copying out of it.
synchronized (_attributes) {
return Collections.enumeration(_attributes.keySet());
}
}
public void
setAttribute(String name, Object value)
throws IllegalStateException
{
if (value==null)
{
// the javadoc says if the value is null, this is the same as
// remove...
removeAttribute(name);
return;
}
if (!isValid()) throw new IllegalStateException();
Object oldValue;
synchronized (_attributes) {
oldValue = _attributes.put(name,value);
}
if (oldValue==value)
{
if (_heldByValue) // TODO
_isDirty=true; // the contents of value may have changed
// perhaps we should raise an 'attributeReplaced' here ?
// this should be configurable - TODO
}
else
{
_isDirty=true;
// perhaps this test should have been 'equals()' - but I
// figure that would be too expensive... - TODO
Object tmp=value; // assume an attribute was added
// notify the old value if necessary
if (oldValue!=null)
{
notifyValueUnbound(name, oldValue);
tmp=oldValue; // actually - an attribute was replaced
}
// notify the new value if necessary
notifyValueBound(name, value);
// notfy the session listeners...
// we don't need to synchronise access to this ArrayList,
// because all modifications to it would have been complete
// before the first request comes through.
if (oldValue==null)
_manager.notifyAttributeAdded(this, name, value);
else
_manager.notifyAttributeReplaced(this, name, oldValue);
}
}
public void
removeAttribute(String name)
throws IllegalStateException
{
if (!isValid()) throw new IllegalStateException();
Object oldValue=null;
synchronized (_attributes) {
oldValue=_attributes.remove(name);
}
if (oldValue!=null)
{
// we did remove an attribute...
_isDirty=true;
// notify the old value if necessary
notifyValueUnbound(name, oldValue);
_manager.notifyAttributeRemoved(this, name, oldValue);
}
}
/**
* @deprecated
*/
public Object
getValue(String name)
throws IllegalStateException
{
return getAttribute(name);
}
/**
* @deprecated
*/
public String[]
getValueNames()
throws IllegalStateException
{
if (!isValid()) throw new IllegalStateException();
// ouch - this is an expensive one...
synchronized (_attributes) {
return (String[])_attributes.keySet().toArray(new String[_attributes.size()]);
}
}
/**
* @deprecated
*/
public void
putValue(String name, Object value)
throws IllegalStateException
{
setAttribute(name, value);
}
/**
* @deprecated
*/
public void
removeValue(String name)
throws IllegalStateException
{
removeAttribute(name);
}
//----------------------------------------
// IMPLEMENTATION
//----------------------------------------
// notification
protected void
notifyValueBound(String name, Object value)
{
if (value instanceof HttpSessionBindingListener)
((HttpSessionBindingListener)value)
.valueBound(new HttpSessionBindingEvent(this,name));
}
protected void
notifyValueUnbound(String name, Object value)
{
if (value instanceof HttpSessionBindingListener)
((HttpSessionBindingListener)value)
.valueUnbound(new HttpSessionBindingEvent(this,name));
}
protected void
notifySessionDidActivate(Object attribute, HttpSessionEvent event, boolean warn)
{
if (attribute instanceof HttpSessionActivationListener)
{
_log.warn("WARNING: About to activate a session attribute that was not
passivated: "+attribute);
_log.warn("WARNING: This was probably due to an uncontrolled server
shutdown.");
((HttpSessionActivationListener)attribute)
.sessionDidActivate(event);
}
}
protected void
notifySessionWillPassivate(Object attribute, HttpSessionEvent event)
{
if (attribute instanceof HttpSessionActivationListener)
((HttpSessionActivationListener)attribute)
.sessionWillPassivate(event);
}
//----------------------------------------
protected void
snapshot()
{
store(false, false);
}
protected void
passivate()
{
store(true, true);
}
protected synchronized void
store(boolean notifyAttributes, boolean shutdown)
{
// if there are no changes and this is not the final passivation
// during a controlled shutdown of webapp or webcontainer, we
// can avoid doing any work. - check - TODO.
if (!_isDirty && !shutdown)
return;
if (_manager.getStore()!=null)
{
// whilst this method is synchronized, _attributes may still be
// modified via their public access which only synchronise on
// _attributes itself. In order to cause as little contention for
// the _attributes map as possible I am going to try taking a copy
// here - otherwise we will have to hang on to a ock on it for
// ages.
// copy is a shallow, not deep, copy. If attributes within it
// are being modified by another thread whilst being
// passivated, it is their responsibility to synchronise so
// that we see a consistent state...
HashMap copy=null;
synchronized (_attributes) {
int size=_attributes.size();
if (size>0)
{
copy=new HashMap(size);
copy.putAll(_attributes);
}
}
if (notifyAttributes && copy!=null)
{
// send passivate events to listening attributes...
HttpSessionEvent event=new HttpSessionEvent(this);
Iterator i = copy.values().iterator();
try
{
while (i.hasNext())
notifySessionWillPassivate(i.next(), event);
}
catch (Exception e)
{
_log.error("Problem whilst passivating session attributes", e);
}
}
// create distributed store and dump data into it.
// how could we ensure this was a single transaction - TODO
AbstractHttpSessionData data=_manager.getStore().make();
data.setId(getId());
data.setAttributes(copy);
data.setCreationTime(getCreationTime());
data.setLastAccessedTime(getLastAccessedTime());
data.setMaxInactiveInterval(getMaxInactiveInterval());
data.setAttributesWerePassivated(notifyAttributes);
_manager.getStore().set(getId(), data);
_log.info("Session passivated: "+getId());
}
}
//----------------------------------------
}
1.1
contrib/jetty/src/main/org/jboss/jetty/session/DistributedHttpSessionManager.java
Index: DistributedHttpSessionManager.java
===================================================================
/*
* jBoss, the OpenSource EJB server
*
* Distributable under GPL license.
* See terms of license at gnu.org.
*/
// $Id: DistributedHttpSessionManager.java,v 1.1 2002/01/13 13:26:45 jules_gosnell
Exp $
// TODO
// keep all state in HttpSessionState objects
// keep all HttpSessionState instances in Store
// rename a few things
// test....
// release !!!!
//----------------------------------------
package org.jboss.jetty.session;
//----------------------------------------
import java.util.ArrayList;
import java.util.EventListener;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import org.jboss.jetty.JBossWebApplicationContext;
import org.jboss.jetty.util.AbstractTimeOutManager;
import org.jboss.jetty.util.NaiveTimeOutManager;
import org.jboss.logging.Logger;
import org.mortbay.jetty.servlet.ServletHandler;
//------------------------------------------------------------------------------
/**
*
* @version $Id: DistributedHttpSessionManager.java,v 1.1 2002/01/13 13:26:45
jules_gosnell Exp $
* @author [EMAIL PROTECTED]
*/
public class DistributedHttpSessionManager
implements org.mortbay.jetty.servlet.SessionManager
{
static AbstractStore _store =new
EJBDistributedStore(); // hardwired for the moment - TODO
final AbstractTimeOutManager _scavenger =new
NaiveTimeOutManager(5*1000, new MyTimeOutNotifier());
final Logger _log;
final JBossWebApplicationContext _context;
final ServletHandler _handler;
final List _sessionListeners =new ArrayList();
final List _sessionAttributeListeners =new ArrayList();
final Map _sessions =new HashMap();
final boolean _isDistributed =true;
volatile boolean _isStarted =false; // TODO
volatile int _dftMaxIdleSecs =-1; // negative means
never timeout...
//----------------------------------------
class MyTimeOutNotifier
implements AbstractTimeOutManager.TimeOutNotifier
{
public void
timeOut(Object object)
{
destroyHttpSession((DistributedHttpSession)object);
}
}
//----------------------------------------
public
DistributedHttpSessionManager(JBossWebApplicationContext context)
{
_context=context;
_handler=_context.getServletHandler();
_log =Logger.getLogger(getClass().getName()+"#" +_context.getContextPath());
}
//----------------------------------------
// factory
public HttpSession
newHttpSession()
{
String id=getStore().nextId();
DistributedHttpSession session = new DistributedHttpSession(this, id,
_dftMaxIdleSecs);
putSession(id,session);
notifySessionCreated(session);
getScavenger().register(session, System.currentTimeMillis(),
_dftMaxIdleSecs*1000);
return session;
}
public void
destroyHttpSession(DistributedHttpSession session)
{
getScavenger().deregister(session);
removeSession(session);
notifySessionDestroyed(session);
session.destroy();
}
public void
passivateHttpSession(DistributedHttpSession session)
{
getScavenger().deregister(session);
removeSession(session);
session.passivate();
}
public HttpSession
getHttpSession(String id) // hopefully local and distributed store will become
same
{
DistributedHttpSession session=null;
// 1. check local store
session = getSession(id);
// 2. check distributed store
if (session==null && _store!=null)
{
session=new DistributedHttpSession(this, id, _store.get(id));
putSession(id,session);
notifySessionCreated(session);
getScavenger().register(session, System.currentTimeMillis(),
session.getMaxInactiveInterval()*1000);
}
return session;
}
// wrappers to access session table...
DistributedHttpSession
getSession(String id)
{
synchronized (_sessions) {return (DistributedHttpSession)_sessions.get(id);}
}
void
putSession(String id, DistributedHttpSession session)
{
synchronized (_sessions) {_sessions.put(id, session);}
}
void
removeSession(DistributedHttpSession session)
{
synchronized (_sessions) {_sessions.remove(session.getId());}
}
//----------------------------------------
// lifecycle
public boolean
isStarted()
{
return _isStarted;
}
public void
start()
throws Exception
{
_isStarted=true;
// sessions are activated lazily...
}
public void
stop() // TODO
{
_isStarted=false; // TODO
// tidy up all sessions...
List sessions = new ArrayList(_sessions.values());
for (Iterator i = sessions.iterator(); i.hasNext(); )
{
DistributedHttpSession session=(DistributedHttpSession)i.next();
if (_isDistributed)
passivateHttpSession(session);
else
destroyHttpSession(session);
}
}
//----------------------------------------
// SessionManager API
public synchronized
void setMaxInactiveInterval(int seconds) // TODO
{
_dftMaxIdleSecs = seconds;
}
//----------------------------------------
// extra accessors to make relationship with session more
// explicit...
AbstractTimeOutManager
getScavenger() // TODO
{
return _scavenger;
}
AbstractStore
getStore() // TODO
{
return _store;
}
ServletHandler
getHandler() // TODO
{
return _handler;
}
ServletContext
getServletContext() // TODO
{
return getHandler().getServletContext();
}
//----------------------------------------
// Listeners
// Listeners are added before requests start coming in, so this code
// needs no synchronisation...
public void
addEventListener(EventListener listener)
throws IllegalArgumentException
{
boolean known=false;
if (listener instanceof HttpSessionAttributeListener)
{
_sessionAttributeListeners.add(listener);
known=true;
}
if (listener instanceof HttpSessionListener)
{
_sessionListeners.add(listener);
known=true;
}
if (!known)
throw new IllegalArgumentException("Unknown listener "+listener);
}
public void
removeEventListener(EventListener listener)
{
if (listener instanceof HttpSessionAttributeListener)
_sessionAttributeListeners.remove(listener);
if (listener instanceof HttpSessionListener)
_sessionListeners.remove(listener);
}
void
notifyAttributeAdded(HttpSession session, String key, Object value)
{
if (_sessionAttributeListeners.size()>0)
{
HttpSessionBindingEvent event =
new HttpSessionBindingEvent(session, key, value);
for(int i=0;i<_sessionAttributeListeners.size();i++)
((HttpSessionAttributeListener)
_sessionAttributeListeners.get(i)).attributeAdded(event);
}
}
void
notifyAttributeReplaced(HttpSession session, String key, Object value)
{
if (_sessionAttributeListeners.size()>0)
{
HttpSessionBindingEvent event =
new HttpSessionBindingEvent(session, key, value);
for(int i=0;i<_sessionAttributeListeners.size();i++)
((HttpSessionAttributeListener)
_sessionAttributeListeners.get(i)).attributeReplaced(event);
}
}
void
notifyAttributeRemoved(HttpSession session, String key, Object value)
{
if (_sessionAttributeListeners.size()>0)
{
HttpSessionBindingEvent event =
new HttpSessionBindingEvent(session, key, value);
for(int i=0;i<_sessionAttributeListeners.size();i++)
((HttpSessionAttributeListener)
_sessionAttributeListeners.get(i)).attributeRemoved(event);
}
}
void
notifySessionCreated(HttpSession session)
{
if (_sessionListeners.size()>0)
{
HttpSessionEvent event = new HttpSessionEvent(session);
for(int i=0;i<_sessionListeners.size();i++)
((HttpSessionListener)_sessionListeners.get(i))
.sessionCreated(event);
}
}
void
notifySessionDestroyed(HttpSession session)
{
if (_sessionListeners.size()>0)
{
HttpSessionEvent event = new HttpSessionEvent(session);
for(int i=0;i<_sessionListeners.size();i++)
((HttpSessionListener)_sessionListeners.get(i))
.sessionDestroyed(event);
}
}
}
_______________________________________________
Jboss-development mailing list
[EMAIL PROTECTED]
https://lists.sourceforge.net/lists/listinfo/jboss-development