Package org.apache.felix.coordinator.impl

Source Code of org.apache.felix.coordinator.impl.CoordinationImpl

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.apache.felix.coordinator.impl;

import java.lang.ref.WeakReference;
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.TimerTask;

import org.osgi.framework.Bundle;
import org.osgi.service.coordinator.Coordination;
import org.osgi.service.coordinator.CoordinationException;
import org.osgi.service.coordinator.CoordinationPermission;
import org.osgi.service.coordinator.Participant;

public class CoordinationImpl implements Coordination
{

    private enum State {
        /** Active */
        ACTIVE,

        /** failed() called */
        FAILED,

        /** Coordination termination started */
        TERMINATING,

        /** Coordination completed */
        TERMINATED
    }

    private final WeakReference<CoordinationHolder> holderRef;

    private final CoordinatorImpl owner;

    private final long id;

    private final String name;

    private long deadLine;

    /**
     * Access to this field must be synchronized as long as the expected state
     * is {@link State#ACTIVE}. Once the state has changed, further updates to this
     * instance will not take place any more and the state will only be modified
     * by the thread successfully setting the state to {@link State#TERMINATING}.
     */
    private volatile State state;

    private Throwable failReason;

    private final ArrayList<Participant> participants;

    private final Map<Class<?>, Object> variables;

    private TimerTask timeoutTask;

    private Thread associatedThread;

    private final Object waitLock = new Object();

    public CoordinationImpl(final CoordinatorImpl owner, final long id, final String name, final long timeOutInMs)
    {
        this.owner = owner;
        this.id = id;
        this.name = name;
        this.state = State.ACTIVE;
        this.participants = new ArrayList<Participant>();
        this.variables = new HashMap<Class<?>, Object>();
        this.deadLine = (timeOutInMs > 0) ? System.currentTimeMillis() + timeOutInMs : 0;

        this.holderRef = new WeakReference<CoordinationHolder>(new CoordinationHolder(this));

        scheduleTimeout(deadLine);
    }

    /**
     * @see org.osgi.service.coordinator.Coordination#getId()
     */
    public long getId()
    {
        return this.id;
    }

    /**
     * @see org.osgi.service.coordinator.Coordination#getName()
     */
    public String getName()
    {
        return name;
    }

    /**
     * @see org.osgi.service.coordinator.Coordination#fail(java.lang.Throwable)
     */
    public boolean fail(final Throwable reason)
    {
        this.owner.checkPermission(name, CoordinationPermission.PARTICIPATE);
        if ( reason == null)
        {
            throw new IllegalArgumentException("Reason must not be null");
        }
        if (startTermination())
        {
            this.failReason = reason;

            final List<Participant> releaseList = new ArrayList<Participant>();
            synchronized ( this.participants )
            {
                releaseList.addAll(this.participants);
                this.participants.clear();
            }
            // consider failure reason (if not null)
            for (int i=releaseList.size()-1;i>=0;i--)
            {
                final Participant part = releaseList.get(i);
                try
                {
                    part.failed(this);
                }
                catch (final Exception e)
                {
                    LogWrapper.getLogger()
                        .log(LogWrapper.LOG_ERROR, "Participant threw exception during call to fail()", e);
                }

                // release the participant for other coordinations
                owner.releaseParticipant(part);
            }

            this.owner.unregister(this, false);
            state = State.FAILED;

            synchronized (this.waitLock)
            {
                this.waitLock.notifyAll();
            }

            return true;
        }
        return false;
    }

    /**
     * @see org.osgi.service.coordinator.Coordination#end()
     */
    public void end()
    {
        this.owner.checkPermission(name, CoordinationPermission.INITIATE);
        if ( !this.isTerminated() && this.associatedThread != null && Thread.currentThread() != this.associatedThread )
        {
            throw new CoordinationException("Coordination is associated with different thread", this, CoordinationException.WRONG_THREAD);
        }

        if (startTermination())
        {

            final CoordinationException nestedFailed = this.owner.endNestedCoordinations(this);
            if ( nestedFailed != null )
            {
                this.failReason = nestedFailed;
            }
            boolean partialFailure = false;
            this.owner.unregister(this, true);

            final List<Participant> releaseList = new ArrayList<Participant>();
            synchronized ( this.participants )
            {
                releaseList.addAll(this.participants);
                this.participants.clear();
            }
            // consider failure reason (if not null)
            for (int i=releaseList.size()-1;i>=0;i--)
            {
                final Participant part = releaseList.get(i);
                try
                {
                    if ( this.failReason != null )
                    {
                        part.failed(this);
                    }
                    else
                    {
                        part.ended(this);
                    }
                }
                catch (final Exception e)
                {
                    LogWrapper.getLogger()
                        .log(LogWrapper.LOG_ERROR, "Participant threw exception during call to fail()", e);
                    partialFailure = true;
                }

                // release the participant for other coordinations
                owner.releaseParticipant(part);
            }

            state = State.TERMINATED;

            synchronized (this.waitLock)
            {
                this.waitLock.notifyAll();
            }

            this.associatedThread = null;

            if ( this.failReason != null )
            {
                throw new CoordinationException("Nested coordination failed", this,
                        CoordinationException.FAILED, this.failReason);
            }
            if (partialFailure)
            {
                throw new CoordinationException("One or more participants threw while ending the coordination", this,
                    CoordinationException.PARTIALLY_ENDED);
            }
        }
        else if ( state == State.FAILED )
        {
            this.owner.unregister(this, true);
            state = State.TERMINATED;
            throw new CoordinationException("Coordination failed", this, CoordinationException.FAILED, failReason);
        }
        else
        {
            // already terminated
            throw new CoordinationException("Coordination " + id + "/" + name + " has already terminated", this,
                CoordinationException.ALREADY_ENDED);
        }
    }


    /**
     * @see org.osgi.service.coordinator.Coordination#getParticipants()
     */
    public List<Participant> getParticipants()
    {
        this.owner.checkPermission(name, CoordinationPermission.INITIATE);
        // synchronize access to the state to prevent it from being changed
        // while we create a copy of the participant list
        synchronized (this)
        {
            if (state == State.ACTIVE)
            {
                synchronized ( this.participants )
                {
                    return new ArrayList<Participant>(participants);
                }
            }
        }

        return Collections.<Participant> emptyList();
    }

    /**
     * @see org.osgi.service.coordinator.Coordination#getFailure()
     */
    public Throwable getFailure()
    {
        this.owner.checkPermission(name, CoordinationPermission.INITIATE);
        return failReason;
    }


    /**
     * @see org.osgi.service.coordinator.Coordination#addParticipant(org.osgi.service.coordinator.Participant)
     */
    public void addParticipant(final Participant p)
    {
        this.owner.checkPermission(name, CoordinationPermission.PARTICIPATE);
        if ( p == null ) {
            throw new IllegalArgumentException("Participant must not be null");
        }
        // ensure participant only participates on a single coordination
        // this blocks until the participant can participate or until
        // a timeout occurs (or a deadlock is detected)
        owner.lockParticipant(p, this);

        // synchronize access to the state to prevent it from being changed
        // while adding the participant
        synchronized (this)
        {
            if (isTerminated())
            {
                owner.releaseParticipant(p);

                throw new CoordinationException("Cannot add Participant " + p + " to terminated Coordination", this,
                    (getFailure() != null) ? CoordinationException.FAILED : CoordinationException.ALREADY_ENDED, getFailure());
            }

            synchronized ( this.participants )
            {
                boolean found = false;
                final Iterator<Participant> iter = this.participants.iterator();
                while ( !found && iter.hasNext() )
                {
                    if ( iter.next() == p )
                    {
                        found = true;
                    }
                }
                if (!found)
                {
                    participants.add(p);
                }

            }
        }
    }

    /**
     * @see org.osgi.service.coordinator.Coordination#getVariables()
     */
    public Map<Class<?>, Object> getVariables()
    {
        this.owner.checkPermission(name, CoordinationPermission.PARTICIPATE);
        return variables;
    }

    /**
     * @see org.osgi.service.coordinator.Coordination#extendTimeout(long)
     */
    public long extendTimeout(final long timeOutInMs)
    {
        this.owner.checkPermission(name, CoordinationPermission.PARTICIPATE);
        if ( timeOutInMs < 0 )
        {
            throw new IllegalArgumentException("Timeout must not be negative");
        }
        if ( this.deadLine > 0 )
        {
            synchronized (this)
            {
                if (isTerminated())
                {
                    throw new CoordinationException("Cannot extend timeout on terminated Coordination", this,
                        (getFailure() != null) ? CoordinationException.FAILED : CoordinationException.ALREADY_ENDED, getFailure());
                }

                if (timeOutInMs > 0)
                {
                    this.deadLine += timeOutInMs;
                    scheduleTimeout(this.deadLine);
                }

            }
        }
        return this.deadLine;
    }

    /**
     * @see org.osgi.service.coordinator.Coordination#isTerminated()
     */
    public boolean isTerminated()
    {
        return state != State.ACTIVE;
    }

    /**
     * @see org.osgi.service.coordinator.Coordination#getThread()
     */
    public Thread getThread()
    {
        this.owner.checkPermission(name, CoordinationPermission.ADMIN);
        return associatedThread;
    }

    /**
     * @see org.osgi.service.coordinator.Coordination#join(long)
     */
    public void join(final long timeOutInMs) throws InterruptedException
    {
        this.owner.checkPermission(name, CoordinationPermission.PARTICIPATE);
        if ( timeOutInMs < 0 )
        {
            throw new IllegalArgumentException("Timeout must not be negative");
        }

        if ( !isTerminated() )
        {
            synchronized ( this.waitLock )
            {
                this.waitLock.wait(timeOutInMs);
            }
        }
    }

    /**
     * @see org.osgi.service.coordinator.Coordination#push()
     */
    public Coordination push()
    {
        this.owner.checkPermission(name, CoordinationPermission.INITIATE);
      if ( isTerminated() )
      {
            throw new CoordinationException("Coordination already ended", this, CoordinationException.ALREADY_ENDED);
      }

        owner.push(this);
        return this;
    }

    /**
     * @see org.osgi.service.coordinator.Coordination#getBundle()
     */
    public Bundle getBundle()
    {
        this.owner.checkPermission(name, CoordinationPermission.ADMIN);
        return this.owner.getBundle();
    }

    /**
     * @see org.osgi.service.coordinator.Coordination#getEnclosingCoordination()
     */
    public Coordination getEnclosingCoordination()
    {
        this.owner.checkPermission(name, CoordinationPermission.ADMIN);
        Coordination c = this.owner.getEnclosingCoordination(this);
        if ( c != null )
        {
            c = ((CoordinationImpl)c).holderRef.get();
        }
        return c;
    }

    //-------

    /**
     * Initiates a coordination timeout. Called from the timer task scheduled by
     * the {@link #scheduleTimeout(long)} method.
     * <p>
     * This method is intended to only be called from the scheduled timer task.
     */
    private void timeout()
    {
        // Fail the Coordination upon timeout
        fail(TIMEOUT);
    }

    /**
     * If this coordination is still active, this method initiates the
     * termination of the coordination by setting the state to
     * {@value State#TERMINATING}, unregistering from the
     * {@link CoordinationMgr} and ensuring there is no timeout task active any
     * longer to timeout this coordination.
     *
     * @return <code>true</code> If the coordination was active and termination
     *         can continue. If <code>false</code> is returned, the coordination
     *         must be considered terminated (or terminating) in the current
     *         thread and no further termination processing must take place.
     */
    private synchronized boolean startTermination()
    {
        if (state == State.ACTIVE)
        {
            state = State.TERMINATING;
            scheduleTimeout(-1);
            return true;
        }

        // this coordination is not active any longer, nothing to do
        return false;
    }

    /**
     * Helper method for timeout scheduling. If a timer is currently scheduled
     * it is canceled. If the new timeout value is a positive value a new timer
     * is scheduled to fire at the desired time (in the future)
     *
     * @param deadline The at which to schedule the timer
     */
    private void scheduleTimeout(final long deadLine)
    {
        if (timeoutTask != null)
        {
            owner.schedule(timeoutTask, -1);
            timeoutTask = null;
        }

        if (deadLine > System.currentTimeMillis())
        {
            timeoutTask = new TimerTask()
            {
                @Override
                public void run()
                {
                    CoordinationImpl.this.timeout();
                }
            };

            owner.schedule(timeoutTask, deadLine);
        }
    }

  @Override
  public int hashCode()
  {
    final int prime = 31;
    int result = 1;
    result = prime * result + (int) (id ^ (id >>> 32));
    return result;
  }

  @Override
  public boolean equals(final Object obj)
  {
    if (obj instanceof CoordinationHolder )
    {
        return obj.equals(this);
    }
        if ( !(obj instanceof CoordinationImpl) )
        {
      return false;
        }
    final CoordinationImpl other = (CoordinationImpl) obj;
    return id == other.id;
  }

  void setAssociatedThread(final Thread t) {
      this.associatedThread = t;
  }

    public Coordination getHolder() {
        return this.holderRef.get();
    }
}
TOP

Related Classes of org.apache.felix.coordinator.impl.CoordinationImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.