/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2011 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.glassfish.deployapi;
import java.util.Collection;
import java.util.Vector;
import java.util.Iterator;
import javax.enterprise.deploy.spi.status.ProgressListener;
import javax.enterprise.deploy.spi.status.ProgressEvent;
import javax.enterprise.deploy.spi.status.ProgressObject;
import javax.enterprise.deploy.spi.status.DeploymentStatus;
import javax.enterprise.deploy.spi.TargetModuleID;
import javax.enterprise.deploy.shared.StateType;
import javax.enterprise.deploy.spi.exceptions.OperationUnsupportedException;
import org.glassfish.deployment.client.DFProgressObject;
import org.glassfish.deployment.client.DFDeploymentStatus;
import org.glassfish.deployment.client.DFDeploymentStatus.Status;
import com.sun.enterprise.util.LocalStringManagerImpl;
/**
* This class acts as a sink for ProgressObject. It registers itself
* as ProgressObject listener for multiple deployment actions and
* tunnel all events to registered ProgressObject listener.
*<p>
*Whenever this class receives a progress event from one of its sources (one of the deploymentFacility
*actions) it forwards that event on to the sink's listeners, changing the state of the event to
*"running." Then, after the sink receives the completion or failure event from the last source,
*it forwards that event as running (as it had all earlier events) and then sends one final
*aggregate completion or failure event.
*<p>
*The sink always follows this pattern, even if it encapsulates only a single source. JSR88 clients should
*be aware of this behavior.
*
* @author Jerome Dochez
*/
public class ProgressObjectSink extends DFProgressObject implements ProgressListener {
private static String LINE_SEPARATOR = System.getProperty("line.separator");
private Vector registeredPL = new Vector();
private Vector deliveredEvents = new Vector();
/* aggregate state starts as successful and is changed only if at least one source operation fails */
private StateType finalStateType = StateType.COMPLETED;
private String finalMessage;
private Vector targetModuleIDs = new Vector();
private Vector sources = new Vector();
private static LocalStringManagerImpl localStrings =
new LocalStringManagerImpl(ProgressObjectSink.class);
DFDeploymentStatus completedStatus = new DFDeploymentStatus();
private boolean completedStatusReady = false;
/**
* register to a new ProgressObject for ProgressEvent notifications
*/
public void sinkProgressObject(ProgressObject source) {
/*
*The following two statements must appear in the order shown. Otherwise, a race condition can exist.
*/
sources.add(source);
source.addProgressListener(this);
}
/**
* receives notification of a progress event from one of our
* registered interface.
*/
public void handleProgressEvent(ProgressEvent progressEvent) {
ProgressEvent forwardedEvent;
DeploymentStatus forwardedDS = progressEvent.getDeploymentStatus();
// we intercept all events...
if (!forwardedDS.isRunning()) {
// this mean we are either completed or failed...
if (forwardedDS.isFailed()) {
/*
*Once at least one operation fails, we know that the aggregate state will have
*to be failed.
*/
finalStateType = StateType.FAILED;
}
// since this is the completion event
// we are done with that progress listener;
Object source = progressEvent.getSource();
if (source instanceof ProgressObject) {
ProgressObject po = (ProgressObject) source;
po.removeProgressListener(this);
sources.remove(source);
if (forwardedDS.isCompleted()) {
TargetModuleID[] ids = po.getResultTargetModuleIDs();
for (int i=0;i<ids.length;i++) {
targetModuleIDs.add(ids[i]);
}
}
} else {
throw new RuntimeException(localStrings.getLocalString(
"enterprise.deployment.client.noprogressobject",
"Progress event does not contain a ProgressObject source"
));
}
/*
*Update the completionStatus by adding a stage to it and recording the completion
*of this event as the newest stage.
*/
updateCompletedStatus(forwardedDS);
// now we change our event state to running. We always forward every event from a
// source to the listeners with "running" status because the sink is not yet completely
// finished. We will also send a final aggregate completion event
// if this is a completion event from our last source (see below).
DeploymentStatusImpl forwardedStatus = new DeploymentStatusImpl();
forwardedStatus.setState(StateType.RUNNING);
forwardedStatus.setMessage(forwardedDS.getMessage());
forwardedStatus.setCommand(forwardedDS.getCommand());
forwardedEvent = new ProgressEvent(this, progressEvent.getTargetModuleID(), forwardedStatus);
} else {
// This is a "running" event from one of our sources, so we just need to swap the source...
forwardedEvent = new ProgressEvent(this, progressEvent.getTargetModuleID(),
forwardedDS);
}
// we need to fire the received event to our listeners
Collection clone;
ProgressEvent finalEvent = null;
synchronized(registeredPL) {
clone = (Collection) registeredPL.clone();
deliveredEvents.add(forwardedEvent);
/*
*If we are done with all of our sources, let's wrap up by creating a final event that will
*be broadcast to the listeners along with the forwarded event. Also create the completed status
*that meets the requirements of the JESProgressObject interface.
*/
if (sources.isEmpty()) {
prepareCompletedStatus();
DeploymentStatusImpl status = new DeploymentStatusImpl();
status.setState(finalStateType);
if (finalStateType.equals(StateType.FAILED)) {
status.setMessage(localStrings.getLocalString(
"enterprise.deployment.client.aggregatefailure",
"At least one operation failed"
));
} else {
status.setMessage(localStrings.getLocalString(
"enterprise.deployment.client.aggregatesuccess",
"All operations completed successfully"
));
}
finalEvent = new ProgressEvent(this, progressEvent.getTargetModuleID(), status);
deliveredEvents.add(finalEvent);
}
}
for (Iterator itr=clone.iterator();itr.hasNext();) {
ProgressListener pl = (ProgressListener) itr.next();
pl.handleProgressEvent(forwardedEvent);
}
/*
*Send the final event if there is one.
*/
if (finalEvent != null) {
for (Iterator itr=clone.iterator();itr.hasNext();) {
ProgressListener pl = (ProgressListener) itr.next();
pl.handleProgressEvent(finalEvent);
}
}
}
/**
* Register a new ProgressListener
* @param the new listener instance
*/
public void addProgressListener(ProgressListener progressListener) {
Collection clone;
synchronized(registeredPL) {
registeredPL.add(progressListener);
// now let's deliver all the events we already received.
clone = (Collection) deliveredEvents.clone();
}
for (Iterator itr=clone.iterator();itr.hasNext();) {
ProgressEvent pe = (ProgressEvent) itr.next();
progressListener.handleProgressEvent(pe);
}
}
/**
* removes a ProgressListener from our list of listeners
* @param the ProgressListener to remove
*/
public void removeProgressListener(ProgressListener progressListener) {
registeredPL.remove(progressListener);
}
public javax.enterprise.deploy.spi.status.ClientConfiguration getClientConfiguration(TargetModuleID targetModuleID) {
// since we are never called upon deploying, I don't
// have to deal with this at this time.
return null;
}
public DeploymentStatus getDeploymentStatus() {
DeploymentStatusImpl status = new DeploymentStatusImpl();
if (sources.isEmpty()) {
status.setState(finalStateType);
status.setMessage(finalMessage);
} else {
status.setState(StateType.RUNNING);
}
return status;
}
public TargetModuleID[] getResultTargetModuleIDs() {
TargetModuleID[] ids = new TargetModuleID[targetModuleIDs.size()];
targetModuleIDs.copyInto(ids);
return ids;
}
public boolean isCancelSupported() {
// if only one of our sources does not support cancel, we don't
for (Iterator itr=getSources().iterator();itr.hasNext();) {
ProgressObject source = (ProgressObject) itr.next();
if (!source.isCancelSupported()) {
return false;
}
}
return true;
}
public boolean isStopSupported() {
// if only one of our sources does not support stop, we don't
for (Iterator itr=getSources().iterator();itr.hasNext();) {
ProgressObject source = (ProgressObject) itr.next();
if (!source.isStopSupported()) {
return false;
}
}
return true;
}
public void cancel() throws OperationUnsupportedException {
if (!isCancelSupported()) {
throw new OperationUnsupportedException("cancel");
}
for (Iterator itr=getSources().iterator();itr.hasNext();) {
ProgressObject source = (ProgressObject) itr.next();
source.cancel();
}
}
public void stop() throws OperationUnsupportedException {
if (!isStopSupported()) {
throw new OperationUnsupportedException("stop");
}
for (Iterator itr=getSources().iterator();itr.hasNext();) {
ProgressObject source = (ProgressObject) itr.next();
source.stop();
}
}
private Collection getSources() {
return (Collection) sources.clone();
}
private void prepareCompletedStatus() {
/*
*The substages may have status values of success when in fact a warning is present
*in a substage. Traverse all the substages, composing the true aggregate state and
*message based on the most severe state that is present in the entire stage tree.
*/
Status worstStatus = Status.NOTINITIALIZED;
StringBuffer msgs = new StringBuffer();
Status newWorstStatus = aggregateStages(worstStatus, msgs, completedStatus);
completedStatus.setStageStatus(newWorstStatus);
completedStatus.setStageStatusMessage(msgs.toString());
completedStatusReady = true;
}
private Status aggregateStages(Status worstStatusSoFar, StringBuffer msgs, DFDeploymentStatus stage) {
/*
*Starting with the stage passed in, see if its severity is more urgent than that seen so far.
*If so, then discard the messages accumulated so far for the less urgent severity and save
*this stage's message and severity as the worst seen so far.
*/
Status stageStatus = stage.getStageStatus();
if (stageStatus.isWorseThan(worstStatusSoFar)) {
worstStatusSoFar = stageStatus;
msgs.delete(0,msgs.length());
}
/*
*If the stage's severity is the same as the currently worst seen, then add this stage's message
*to the aggregate message.
*/
if (stageStatus == worstStatusSoFar) {
msgs.append(stage.getStageStatusMessage()).append(LINE_SEPARATOR);
}
/*
*Now, do the same for each substage.
*/
for (Iterator it = stage.getSubStages(); it.hasNext(); ) {
DFDeploymentStatus substage = (DFDeploymentStatus) it.next();
worstStatusSoFar = aggregateStages(worstStatusSoFar, msgs, substage);
}
return worstStatusSoFar;
}
/**
*Report completed status for deploytool.
*@return null if not completed, or the DFDeploymentStatus set to reflect the completion
*/
public DFDeploymentStatus getCompletedStatus() {
DFDeploymentStatus answer = null;
if (completedStatusReady) {
answer = completedStatus;
}
return answer;
}
private void updateCompletedStatus(DeploymentStatus ds) {
/*
*If the status passed in is already a backend.DeploymentStatus then add it as a new stage to the
*completed status. Otherwise, create a new backend.DeploymentStatus, fill it in as much as
*possible, and add it as the next stage.
*/
DFDeploymentStatus newStageStatus = null;
if (ds instanceof DeploymentStatusImpl) {
DeploymentStatusImpl dsi = (DeploymentStatusImpl) ds;
newStageStatus = dsi.progressObject.getCompletedStatus();
} else {
/*
*Create a new status stage and add it to the completed status.
*/
newStageStatus = new DFDeploymentStatus(completedStatus);
/*
*The new state status depends on the DeploymentStatus outcome.
*/
int stageStatus = -1;
Throwable exc;
reviseStatusAndMessage(ds, newStageStatus);
}
if (newStageStatus != null) {
/*
*Update the final status state if this new stage's state is worse than the final status's
*current state.
*/
completedStatus.addSubStage(newStageStatus);
/*
*The status being reported may say it is successful but there could be warnings in substages
*(or substages of substages...). So the truly final state and message for the completed
*status is determined once, after the last source of events is removed.
*/
} else {
System.err.println("A newStageStatus was null");
}
}
private void reviseStatusAndMessage(DeploymentStatus ds, DFDeploymentStatus newStageStatus) {
String msgKey = null;
Status stageStatus;
if (ds.isCompleted()) {
/*
*The deployment status for this source was successful.
*/
msgKey = "enterprise.deployment.client.action_completed";
stageStatus = Status.SUCCESS;
} else {
/*
*The deployment status for this source failed.
*/
msgKey = "enterprise.deployment.client.action_failed";
stageStatus = Status.FAILURE;
}
String i18msg = localStrings.getLocalString(msgKey, ds.getMessage());
newStageStatus.setStageStatus(stageStatus);
newStageStatus.setStageStatusMessage(i18msg);
}
}