RemoteOperator.java :  » Development » rapla » org » rapla » storage » dbrm » Java Open Source

Java Open Source » Development » rapla 
rapla » org » rapla » storage » dbrm » RemoteOperator.java
/*--------------------------------------------------------------------------*
 | Copyright (C) 2006  Christopher Kohlhaas                                 |
 |                                                                          |
 | This program is free software; you can redistribute it and/or modify     |
 | it under the terms of the GNU General Public License as published by the |
 | Free Software Foundation. A copy of the license has been included with   |
 | these distribution in the COPYING file, if not go to www.fsf.org         |
 |                                                                          |
 | As a special exception, you are granted the permissions to link this     |
 | program with every library, which license fulfills the Open Source       |
 | Definition as published by the Open Source Initiative (OSI).             |
 *--------------------------------------------------------------------------*/

package org.rapla.storage.dbrm;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;

import org.apache.avalon.framework.configuration.Configuration;
import org.rapla.components.util.Command;
import org.rapla.components.util.CommandQueue;
import org.rapla.components.util.SerializableDateTimeFormat;
import org.rapla.entities.EntityNotFoundException;
import org.rapla.entities.RaplaType;
import org.rapla.entities.User;
import org.rapla.entities.storage.EntityResolver;
import org.rapla.entities.storage.RefEntity;
import org.rapla.entities.storage.internal.SimpleEntity;
import org.rapla.entities.storage.internal.SimpleIdentifier;
import org.rapla.framework.Container;
import org.rapla.framework.RaplaContext;
import org.rapla.framework.RaplaException;
import org.rapla.plugin.RaplaExtensionPoints;
import org.rapla.server.RemoteMethod;
import org.rapla.server.RemoteServiceCaller;
import org.rapla.server.RemoteStorage;
import org.rapla.server.RestartServer;
import org.rapla.server.internal.RemoteStorageImpl;
import org.rapla.server.internal.SessionExpiredException;
import org.rapla.storage.IOContext;
import org.rapla.storage.LocalCache;
import org.rapla.storage.RaplaSecurityException;
import org.rapla.storage.UpdateEvent;
import org.rapla.storage.UpdateResult;
import org.rapla.storage.impl.AbstractCachableOperator;
import org.rapla.storage.impl.EntityStore;
import org.rapla.storage.xml.RaplaInput;
import org.rapla.storage.xml.RaplaMainReader;

/** This operator can be used to modify and access data over the
 * network.  It needs an server-process providing the StorageService
 * (usually this is the default rapla-server).
 * <p>Sample configuration:
  <pre>
   &lt;remote-storage id="web">
   &lt;/remote-storate>
  </pre>
 * The messaging-client value contains the id of a
 * messaging-client-component which handles the
 * communication with the server.
 * The RemoteOperator provides also the Service {@link RemoteServiceCaller}
 *   @see org.rapla.server.RemoteStorageCallback
 *   @see org.rapla.components.rpc.MessagingClient
*/
public class RemoteOperator
    extends
        AbstractCachableOperator
    implements
    RemoteServiceCaller
          ,RestartServer
{
    ServerStub serv = new ServerStub();
    String username;
    String password;
    protected CommandQueue notifyQueue;
    private boolean bSessionActive = false;
    Connector connector;
    private boolean bReservationsFetched;
    private Date firstCachedDate = null;
    private Date lastCachedDate = null;
    private boolean isRestarting;
    public RemoteOperator(RaplaContext context, Configuration config) throws RaplaException {
        super( context );
        
        Container raplaMainContainer = ((Container)context.lookup( Container.ROLE)); 
        raplaMainContainer.addContainerProvidedComponent( RaplaExtensionPoints.SERVLET_PAGE_EXTENSION, RaplaStorePage.class.getName(), "store", null);
        
        ((Container)context.lookup( Container.ROLE)).addContainerProvidedComponentInstance(RestartServer.ROLE, this);
        ((Container)context.lookup( Container.ROLE)).addContainerProvidedComponentInstance(RemoteServiceCaller.ROLE, this);
        
        connector = new HTTPConnector(context,config);
    }

    public void connect() throws RaplaException {
        throw new RaplaException("RemoteOperator doesn't support anonymous connect");
    }

    public void connect(String username,char[] password) throws RaplaException {
        this.username = username;
        this.password = new String(password);
        if (isConnected())
            return;
        getLogger().info("Connecting to server and starting login..");
        doConnect();
        try {
            String clientVersion= i18n.getString("rapla.version") ;
            serv.checkServerVersion( clientVersion);
            serv.login(this.username,this.password);
            bSessionActive = true;
            updateToday();
            getLogger().info("login successfull");
        } catch (RaplaException ex){
            disconnect();
            throw ex;
        }
        loadData();
        notifyQueue = org.rapla.components.util.CommandQueue.createCommandQueue();
    }

    public void saveData() throws RaplaException {
        throw new RaplaException("RemoteOperator doesn't support storing complete cache, yet!");
    }

    /** implementation specific. Should be private */
    public void serverHangup() {
        getLogger().warn("Server hangup");
        if (!isRestarting) {
            getLogger().error(getI18n().format("error.connection_closed",getConnectionName()));
        }
        isRestarting = false;
        new Thread() {
            public void run() {
                fireStorageDisconnected();
            }
        }.start();
    }


    public String getConnectionName() {
        return connector.getInfo();
    }

    private void doConnect() throws RaplaException {
        boolean bFailed = true;
        try {
            connector.start();
            bFailed = false;
        } catch (Exception e) {
            throw new RaplaException(i18n.format("error.connect",getConnectionName()),e);
        } finally {
            if (bFailed)
                disconnect();
        }
    }

    public boolean isConnected() {
       // return connector.hasSession();//messagingClient != null && messagingClient.isRunning();
        return bSessionActive;
    }

    public boolean supportsActiveMonitoring() {
        return true;
    }

    public void refresh() throws RaplaException {
        serv.refresh();
    }
    public void restartServer() throws RaplaException {
        isRestarting = true;
        serv.restartServer();
        fireStorageDisconnected();
        
    }

    /** disconnect from the server */
    public void disconnect() throws RaplaException {
        getLogger().info("Disconnecting from server");
        try {
            bSessionActive = false;
            if ( notifyQueue != null)
            {
                notifyQueue.dequeueAll(); // Execute all update Commands.
            }
            firstCachedDate = null;
            lastCachedDate = null;
            bReservationsFetched = false;
            connector.stop();
            cache.clearAll();
        } catch (Exception e) {
            throw new RaplaException("Could not disconnect", e);
        }
        fireStorageDisconnected();
    }

    private void addToCache(List list, boolean useCache) throws RaplaException {
        EntityResolver entityResolver = createEntityResolver( list, useCache ? cache : null );
        synchronized (cache) {
          resolveEntities( list.iterator(), entityResolver );
            for( Iterator it = list.iterator();it.hasNext();) {
                SimpleEntity entity = (SimpleEntity) it.next();
                cache.put(entity);
            }
        }
    }

    private void loadData() throws RaplaException {
        checkConnected();
        cache.clearAll();
        getLogger().debug("Getting Data..");
        // recontextualize Entities
        addToCache(serv.getResources(), false );
        getLogger().debug("Data flushed");
    }

    protected void checkConnected() throws RaplaException {
        if ( !bSessionActive ) {
            if (username == null) {
                throw new RaplaException("Need to login first!");
            } else {
                throw new RaplaException(i18n.format("error.connection_closed", getConnectionName()));
            }
        }
    }

    protected long getCurrentTime() throws RaplaException {
        if ( bSessionActive )
            return serv.getServerTime();
        else
            return super.getCurrentTime();
    }


    public void dispatch(UpdateEvent evt) throws RaplaException {
        checkConnected();
        // Create closure
        UpdateEvent closure = createClosure(evt );
        check( closure );
        // Store on server
        if (getLogger().isDebugEnabled()) {
            Iterator it =closure.getStoreObjects().iterator();
            while (it.hasNext()) {
                RefEntity entity = (RefEntity)it.next();
                getLogger().debug("dispatching store for: " + entity);
            }
            it =closure.getRemoveObjects().iterator();
            while (it.hasNext()) {
                RefEntity entity = (RefEntity)it.next();
                getLogger().debug("dispatching remove for: " + entity);
            }
        }
        serv.dispatch( closure );
        // Store in cache
        UpdateResult result = update( closure, true );
        fireStorageUpdated(result);
    }

    public Object createIdentifier(RaplaType raplaType) throws RaplaException {
        return serv.createIdentifier(raplaType);
    }

    /** we must override this method because we can't store the passwords on the client*/
    public void authenticate(String username,String password) throws RaplaException {
        serv.authenticate(username,password);
    }

    public boolean canChangePassword() {
        try {
            return serv.canChangePassword();
        } catch (RaplaException ex) {
            return false;
        }
    }

    public void changePassword(User user,char[] oldPassword,char[] newPassword) throws RaplaException {
        try {
            serv.changePassword(user.getUsername(),oldPassword,newPassword);
        } catch (RaplaSecurityException ex) {
            throw new RaplaSecurityException(i18n.getString("error.wrong_password"));
        }
    }

    private void updateReservations(User user,Date start,Date end) throws RaplaException {
        if ( !bReservationsFetched  ) {
            bReservationsFetched = true;
            firstCachedDate = start;
          lastCachedDate = end;
          addToCache(serv.getReservations(firstCachedDate, lastCachedDate), true );
          return;
        }

        if ( firstCachedDate != null) {
            if (start == null || start.before(firstCachedDate)) {
                addToCache(serv.getReservations( start, firstCachedDate), true );
                firstCachedDate = start;
            }
        }
        if ( lastCachedDate != null) {
            if (end == null || end.after(lastCachedDate)) {
                addToCache(serv.getReservations( lastCachedDate, end), true );
                lastCachedDate = end;
            }
        }
    }

    public RefEntity resolveId(Object id) throws EntityNotFoundException {
        try {
            return super.resolveId(id);
        } catch (EntityNotFoundException ex) {
            try {
                List resolved = serv.getEntityRecursive( id );
                addToCache(resolved, true );
            } catch (RaplaException rex) {
                throw new EntityNotFoundException("Object for id " + id.toString() + " not found due to " + ex.getMessage());
            }
            return super.resolveId(id);
        }
    }

    public SortedSet getAppointments(User user,Date start,Date end) throws RaplaException {
        checkConnected();
        updateReservations( user, start, end );
        return super.getAppointments( user, start, end );
    }

    public List getReservations(User user,Date start,Date end) throws RaplaException {
        checkConnected();
        updateReservations( user, start, end );
        return super.getReservations( user, start, end);
    }
   
    private String readResultToString( InputStream input) throws IOException
    {
        InputStreamReader in = new InputStreamReader( input);
        char[] buf = new char[4096];
        StringBuffer buffer = new StringBuffer();
        while ( true )
        {
            int  len = in.read(buf);
            if ( len == -1)
            {
                break;
            }
            buffer.append( buf, 0,len );
            //buf.
        }
        String result = buffer.toString();
        return result;
    }
   
    public String call( String serviceName, RemoteMethod method, String[] args ) throws RaplaException
    {
        return serv.call(serviceName,method, (String[])args);
    }

    long clientRepositoryVerion = 0;

    public class ServerStub implements RemoteStorage {
        
        String call( RemoteMethod method,String[] args) throws RaplaException {
            return call( null, method, args);
        }
        
        InputStream callInput( RemoteMethod method,String[] args) throws RaplaException {
            return callInput( null, method, args);
        }
        
        String call( String service, RemoteMethod method,String[] args) throws RaplaException {
            try {
                InputStream stream = callInput( service,method,args);
                String result = readResultToString( stream);
  //              System.out.println( result );
                return result;
            } catch (IOException ex) {
                throw new RaplaException(ex);
            }
         
        }
         
        InputStream callInput( String service,RemoteMethod method,String[] args) throws RaplaException {
            try {
                String methodName = method.method();
                Map argMap = createArgumentMap( method, args);
                if ( service != null)
                {
                    methodName = service +"/" + methodName; 
                }
                return connector.call( methodName, argMap );
            } catch (SessionExpiredException ex) {
                disconnect();
                throw ex;
            } catch (IOException ex) {
                throw new RaplaException(ex);
            }
        }
        
        private Map createArgumentMap( RemoteMethod method, String[] args ) throws RaplaException
        {
            Map argMap = new HashMap();
            if ( args.length != method.length())
            {
                throw new RaplaException("Paramter list don't match Expected " + method.length() +" but was " + args.length);
            }
            for ( int i=0;i<args.length;i++)
            {
                String argName = method.arg( i );
                argMap.put(argName, args[i]);
            }
            return argMap;
        }
       

        public void login(String username,String password) throws RaplaException {
            call(LOGIN,new String[] { username,password});
        }

        public void checkServerVersion(String clientVersion) throws RaplaException {
            call(CHECK_SERVER_VERSION, new String[] {clientVersion});
        }

        public void authenticate(String username,String password) throws RaplaException {
            String[] args = new String[] { username,password};
            
            call(AUTHENTICATE,args);
        }

        public long getServerTime() throws RaplaException {
            String timeString = call(GET_SERVER_TIME,new String[] {});
            return Long.parseLong( timeString); 
        }

        public boolean canChangePassword() throws RaplaException {
            String result = call(CAN_CHANGE_PASSWORD, new String[] {});
            return Boolean.valueOf( result).booleanValue();
         }

        public void changePassword(String username,char[] oldPassword,char[] newPassword) throws RaplaException {
            String oldPasswordS = new String(oldPassword);
            String newPasswordS = new String(newPassword);
            call(CHANGE_PASSWORD,new String[] {username, oldPasswordS, newPasswordS});
        }

        public List getEntityRecursive(Object id) throws RaplaException {
            SimpleIdentifier castedId = (SimpleIdentifier) id;
            String idS = castedId.getTypeName() + "_" + String.valueOf(castedId.getKey());
            InputStream stream = callInput( GET_ENTITY_RECURSIVE, new String[] {idS});
            EntityStore store = new EntityStore( cache, cache.getSuperCategory());
            return readIntoStore( stream, store );
        }

        public List getResources() throws RaplaException {
            InputStream stream = callInput( GET_RESOURCES,new String[] {});
            EntityStore store = new EntityStore( null, cache.getSuperCategory());
            return readIntoStore( stream, store );
        }

        public List getReservations(Date start,Date end) throws RaplaException {
            SerializableDateTimeFormat format = new SerializableDateTimeFormat();
            String startS =  null;
            String endS = null;
            if (start!= null)
            {
                startS = format.formatDate( start);
            }
            if ( end != null ) 
            {
                endS = format.formatDate( end);
            }
            InputStream stream = callInput( GET_RESERVATIONS,new String[] {startS, endS});
            EntityStore store = new EntityStore( cache, cache.getSuperCategory());
            return readIntoStore( stream, store );
           
        }

        private List readIntoStore( InputStream stream, EntityStore store ) throws RaplaException
        {
            RaplaContext inputContext = new IOContext().createInputContext(serviceManager,store,idTable);
            RaplaInput xmlAdapter = new RaplaInput( getLogger().getChildLogger("reading"));
            RaplaMainReader contentHandler = new RaplaMainReader( inputContext);
            try
            { 
                xmlAdapter.read(new InputStreamReader( stream,"UTF-8"), contentHandler, false);
            }
            catch (IOException e)
            {
                throw new RaplaException( "Error retrieving Data ", e);
            }
            return new ArrayList(store.getList());
        }

        public Object createIdentifier(RaplaType raplaType) throws RaplaException {
            String raplaTypeS =  raplaType.toString();
            String id = call(CREATE_IDENTIFIER, new String[] {raplaTypeS});
            try
            {
                return LocalCache.getId( raplaType, id);
            }
            catch (ParseException e)
            {
               throw new RaplaException( "Invalid id Object", e);
            }
        }

        public void dispatch(UpdateEvent evt) throws RaplaException {
            try 
            {
                String xml = RemoteStorageImpl.createUpdateEvent( serviceManager,cache,evt );
                call( DISPATCH, new String[] {xml});
            }
            catch (IOException e)
            {
                throw new RaplaException( "Error retrieving Data ", e);
            }
        }

        public void restartServer() throws RaplaException {
            call(RESTART_SERVER,new String[] {});
        }
        
        public void refresh() throws RaplaException {
            String xml = call( REFRESH,new String[] {String.valueOf(clientRepositoryVerion)});
            if ( xml.length() < 50 && xml.indexOf( "<uptodate/>")>=0)
            {
            }
            else
            {
                RemoteOperator.this.refresh( xml);
            }
        }
        public void notifyUpdate() {
            if ( isRestarting)
                return;
            notifyQueue.enqueue(new UpdateCommand());
        }

 
    }

    private void refresh(String xml) throws RaplaException
    {
        synchronized (getLock()) 
        {
            UpdateEvent evt = RemoteStorageImpl.createUpdateEvent( serviceManager,xml, cache );
            Iterator it = evt.getStoreObjects().iterator();
            while (it.hasNext()) {
                SimpleEntity entity = (SimpleEntity) it.next();

                RefEntity cachedVersion = (RefEntity) cache.get(entity.getId());
                // Ignore object if its not newer than the one in cache.
                if (cachedVersion != null && cachedVersion.getVersion() >= entity.getVersion()) {
                    //getLogger().debug("already on client " + entity + " version " + cachedVersion.getVersion());
                    it.remove();
                    continue;
                }

                if (getLogger().isDebugEnabled())
                    getLogger().debug(" storing " + entity.getId()
                                      + " version: " + entity.getVersion());
            }

            RemoteOperator.super.resolveEntities
                (
                 evt.getStoreObjects().iterator()
                 ,createEntityResolver(evt.getStoreObjects(),cache)
                 );

            it = evt.getRemoveObjects().iterator();
            while (it.hasNext()) {
                SimpleEntity entity = (SimpleEntity) it.next();

                RefEntity cachedVersion = (RefEntity) cache.get(entity.getId());
                // Ignore object, if its not in cache.
                if (cachedVersion == null) {
                    it.remove();
                    continue;
                }
                if (getLogger().isDebugEnabled())
                        getLogger().debug(" removing " + entity.getId()
                                          + " version: " + entity.getVersion());
            }

            RemoteOperator.super.resolveEntities
                (
                 evt.getRemoveObjects().iterator()
                 ,createEntityResolver(evt.getStoreObjects(),cache)
                 );

            if ( bSessionActive  &&
                  ( evt.getRemoveObjects().size() > 0
                 || evt.getStoreObjects().size() > 0 )  ) {
                getLogger().info("Objects updated!");
                UpdateResult result = update(evt, false);
                clientRepositoryVerion = evt.getRepositoryVersion();
                // now we can set the cache as updated
                fireStorageUpdated(result);
            }
            clientRepositoryVerion = evt.getRepositoryVersion();
        }
       
    }
    public void serverDisconnected()  {
        bSessionActive = false;
    }

    //******* End ClientInterface *************
    class UpdateCommand implements Command {
          public void execute() {
            if ( !bSessionActive )
                return; // We can ignore the update!
            try {
                serv.refresh();
            } catch (Exception ex) {
                getLogger().error(ex.getMessage(),ex);
        /*
                // #TODO. Do we need do disconnect on every notify error?
                try {
                    disconnect();
                } catch (RaplaException rex) {
                    getLogger().error(rex.getMessage(),rex);
                }
                */
            }
        }
    }

 
  

}

java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.