/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Copyright 2015 The ZAP Development Team
*
* 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.zaproxy.zap.extension.autoupdate;
import java.awt.EventQueue;
import java.awt.Window;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import javax.swing.GroupLayout;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.extension.AbstractDialog;
import org.parosproxy.paros.view.View;
import org.zaproxy.zap.control.AddOn;
import org.zaproxy.zap.control.AddOnUninstallationProgressCallback;
/**
* Progress dialogue for uninstallation of add-ons.
* <p>
* It shows a progress bar, the current add-on being uninstalled and the current and number of components (e.g. extensions,
* files...) uninstalled.
*
* @since 2.4.0
*/
class UninstallationProgressDialogue extends AbstractDialog {
private static final long serialVersionUID = 6544278337930125848L;
private static final int MS_TO_WAIT_BEFORE_SHOW = 500;
private static final int EXTENSION_UNINSTALL_WEIGHT = 10;
private static final int MINIMUM_TO_IMMEDIATELY_SHOW_DIALOGUE = 50;
private JLabel statusLabel;
private JProgressBar progressBar;
private JLabel customLabel;
private UninstallationProgressEvent.Type currentType;
private String keyBaseStatusMessage;
private AddOn currentAddOn;
private boolean update;
private boolean failedUninstallations;
private long startTime;
private boolean done;
private boolean setVisibleInvoked;
private List<AddOnUninstallListener> listeners;
private boolean synchronous;
public UninstallationProgressDialogue(Window parent, Set<AddOn> addOns) {
super(parent, true);
keyBaseStatusMessage = "";
listeners = Collections.emptyList();
setTitle(Constant.messages.getString("cfu.uninstallation.progress.dialogue.title"));
setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
int max = 0;
for (AddOn addOn : addOns) {
max += addOn.getFiles().size();
max += addOn.getAscanrules().size();
max += addOn.getPscanrules().size();
max += addOn.getLoadedExtensions().size() * EXTENSION_UNINSTALL_WEIGHT;
}
getProgressBar().setValue(0);
getProgressBar().setMaximum(max);
getStatusLabel().setText(" ");
getCustomLabel().setText(" ");
JPanel panel = new JPanel();
GroupLayout layout = new GroupLayout(panel);
panel.setLayout(layout);
layout.setAutoCreateGaps(true);
layout.setAutoCreateContainerGaps(true);
layout.setHonorsVisibility(false);
JLabel messageLabel = new JLabel(Constant.messages.getString("cfu.uninstallation.progress.dialogue.uninstalling"));
layout.setHorizontalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addComponent(messageLabel)
.addComponent(getStatusLabel())
.addComponent(getProgressBar(), 200, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE)
.addComponent(getCustomLabel()));
layout.setVerticalGroup(layout.createSequentialGroup()
.addComponent(messageLabel)
.addComponent(getStatusLabel())
.addComponent(getProgressBar())
.addComponent(getCustomLabel()));
setContentPane(panel);
pack();
}
private JLabel getStatusLabel() {
if (statusLabel == null) {
statusLabel = new JLabel();
}
return statusLabel;
}
private JProgressBar getProgressBar() {
if (progressBar == null) {
progressBar = new JProgressBar();
}
return progressBar;
}
private JLabel getCustomLabel() {
if (customLabel == null) {
customLabel = new JLabel();
}
return customLabel;
}
private void incrementProgress(int amount) {
int currentValue = getProgressBar().getValue() + amount;
getProgressBar().setValue(currentValue);
}
private void setCurrentAddOn(AddOn addOn) {
currentAddOn = addOn;
getStatusLabel().setText(
MessageFormat.format(
Constant.messages.getString("cfu.uninstallation.progress.dialogue.currentAddOn"),
addOn.getName(),
Integer.valueOf(addOn.getFileVersion())));
}
private void setCustomMessage(String message) {
getCustomLabel().setText(message);
}
public void addAddOnUninstallListener(AddOnUninstallListener listener) {
if (listeners.isEmpty()) {
listeners = new ArrayList<>(1);
}
listeners.add(listener);
}
/**
* Binds the given uninstallation worker to this dialogue.
* <p>
* The dialogue is disposed once the worker finishes the uninstallation.
*
* @param worker the uninstallation worker
*/
public void bind(SwingWorker<?, UninstallationProgressEvent> worker) {
worker.addPropertyChangeListener(new WaitForDoneWorkerCloseListener());
}
/**
* Sets whether or not the dialogue should be shown synchronously.
* <p>
* If the dialogue is not shown synchronously the dialogue is only shown immediately if the calculated uninstallations will
* take some time otherwise it will not be shown unless it passed a given time. It might happen that the dialogue is not
* shown at all if the uninstallation finishes in a given threshold.
*
* @param synchronous {@code true} if the dialogue should be shown synchronously, {@code false} otherwise
*/
public void setSynchronous(boolean synchronous) {
this.synchronous = synchronous;
}
@Override
public void setVisible(boolean show) {
if (show && !synchronous) {
startTime = System.currentTimeMillis();
done = false;
if (MINIMUM_TO_IMMEDIATELY_SHOW_DIALOGUE > getProgressBar().getMaximum()) {
return;
}
}
super.setVisible(show);
}
/**
* Updates the progress with the given events.
*
* @param events the events generated during uninstallation
*/
public void update(List<UninstallationProgressEvent> events) {
if (!isVisible()) {
if ((System.currentTimeMillis() - startTime) >= MS_TO_WAIT_BEFORE_SHOW) {
if (!done && !setVisibleInvoked) {
setVisibleInvoked = true;
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
if (!done) {
UninstallationProgressDialogue.super.setVisible(true);
}
}
});
}
}
}
int totalAmount = 0;
AddOn addOn = null;
for (UninstallationProgressEvent event : events) {
totalAmount += event.getAmount();
if (UninstallationProgressEvent.Type.FINISHED_ADD_ON == event.getType()) {
for (AddOnUninstallListener listener : listeners) {
failedUninstallations = !event.isUninstalled();
listener.addOnUninstalled(currentAddOn, update, event.isUninstalled());
}
} else if (UninstallationProgressEvent.Type.ADD_ON == event.getType()) {
addOn = event.getAddOn();
currentAddOn = addOn;
update = event.isUpdate();
for (AddOnUninstallListener listener : listeners) {
listener.uninstallingAddOn(addOn, update);
}
}
}
UninstallationProgressEvent last = events.get(events.size() - 1);
if (addOn != null) {
setCurrentAddOn(addOn);
}
if (totalAmount != 0) {
incrementProgress(totalAmount);
}
if (currentType != last.getType()) {
String keyMessage;
switch (last.getType()) {
case FILE:
keyMessage = "cfu.uninstallation.progress.dialogue.uninstallingFile";
break;
case ACTIVE_RULE:
keyMessage = "cfu.uninstallation.progress.dialogue.uninstallingActiveScanner";
break;
case PASSIVE_RULE:
keyMessage = "cfu.uninstallation.progress.dialogue.uninstallingPassiveScanner";
break;
case EXTENSION:
keyMessage = "cfu.uninstallation.progress.dialogue.uninstallingExtension";
break;
default:
keyMessage = "";
break;
}
currentType = last.getType();
keyBaseStatusMessage = keyMessage;
}
if (keyBaseStatusMessage.isEmpty()) {
setCustomMessage("");
} else {
setCustomMessage(MessageFormat.format(
Constant.messages.getString(keyBaseStatusMessage),
Integer.valueOf(last.getValue()),
Integer.valueOf(last.getMax())));
}
}
/**
* A progress uninstallation event.
*/
static final class UninstallationProgressEvent {
private enum Type {
ADD_ON,
FILE,
ACTIVE_RULE,
PASSIVE_RULE,
EXTENSION,
FINISHED_ADD_ON
};
private final AddOn addOn;
private final boolean update;
private final boolean uninstalled;
private final Type type;
private final int amount;
private final int value;
private final int max;
public UninstallationProgressEvent(AddOn addOn, boolean update) {
this.addOn = addOn;
this.update = update;
this.uninstalled = false;
this.amount = 0;
this.type = Type.ADD_ON;
this.value = 0;
this.max = 0;
}
public UninstallationProgressEvent(Type type, int value, int max) {
addOn = null;
update = false;
uninstalled = false;
this.type = type;
amount = (type == Type.EXTENSION) ? EXTENSION_UNINSTALL_WEIGHT : 1;
this.value = value;
this.max = max;
}
public UninstallationProgressEvent(boolean uninstalled) {
this.uninstalled = uninstalled;
this.type = Type.FINISHED_ADD_ON;
this.addOn = null;
this.update = false;
this.amount = 0;
this.value = 0;
this.max = 0;
}
public AddOn getAddOn() {
return addOn;
}
public boolean isUpdate() {
return update;
}
public boolean isUninstalled() {
return uninstalled;
}
public int getAmount() {
return amount;
}
private Type getType() {
return type;
}
public int getValue() {
return value;
}
public int getMax() {
return max;
}
}
public static abstract class UninstallationProgressHandler implements AddOnUninstallationProgressCallback {
protected abstract void publishEvent(UninstallationProgressEvent event);
private UninstallationProgressEvent.Type type = null;
private int currentValue = 0;
private int currentMax = 0;
@Override
public void uninstallingAddOn(AddOn addOn, boolean update) {
publishEvent(new UninstallationProgressEvent(addOn, update));
}
@Override
public void activeScanRulesWillBeRemoved(int numberOfRules) {
resetTypeAndValues(UninstallationProgressEvent.Type.ACTIVE_RULE, numberOfRules);
}
private void resetTypeAndValues(UninstallationProgressEvent.Type type, int max) {
this.type = type;
currentValue = 0;
currentMax = max;
publishEvent(new UninstallationProgressEvent(type, currentValue, currentMax));
}
@Override
public void activeScanRuleRemoved(String name) {
publish();
}
private void publish() {
currentValue++;
publishEvent(new UninstallationProgressEvent(type, currentValue, currentMax));
}
@Override
public void passiveScanRulesWillBeRemoved(int numberOfRules) {
resetTypeAndValues(UninstallationProgressEvent.Type.PASSIVE_RULE, numberOfRules);
}
@Override
public void passiveScanRuleRemoved(String name) {
publish();
}
@Override
public void filesWillBeRemoved(int numberOfFiles) {
resetTypeAndValues(UninstallationProgressEvent.Type.FILE, numberOfFiles);
}
@Override
public void fileRemoved() {
publish();
}
@Override
public void extensionsWillBeRemoved(int numberOfExtensions) {
resetTypeAndValues(UninstallationProgressEvent.Type.EXTENSION, numberOfExtensions);
}
@Override
public void extensionRemoved(String name) {
publish();
}
@Override
public void addOnUninstalled(boolean uninstalled) {
publishEvent(new UninstallationProgressEvent(uninstalled));
}
}
public static interface AddOnUninstallListener {
void uninstallingAddOn(AddOn addOn, boolean updating);
void addOnUninstalled(AddOn addOn, boolean update, boolean uninstalled);
}
private class WaitForDoneWorkerCloseListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent event) {
if ("state".equals(event.getPropertyName()) && SwingWorker.StateValue.DONE == event.getNewValue()) {
setVisible(false);
dispose();
done = true;
if (failedUninstallations) {
View.getSingleton().showWarningDialog(getOwner(), Constant.messages.getString("cfu.unintall.failed"));
}
}
}
}
}