1 /*******************************************************************************
2  *  Copyright (c) 2007, 2017 IBM Corporation and others.
3  *
4  *  This program and the accompanying materials
5  *  are made available under the terms of the Eclipse Public License 2.0
6  *  which accompanies this distribution, and is available at
7  *  https://www.eclipse.org/legal/epl-2.0/
8  *
9  *  SPDX-License-Identifier: EPL-2.0
10  *
11  *  Contributors:
12  *     IBM Corporation - initial API and implementation
13  *******************************************************************************/
14 package org.eclipse.equinox.internal.p2.engine;
15 
16 import java.util.*;
17 import org.eclipse.core.runtime.*;
18 import org.eclipse.equinox.p2.engine.*;
19 import org.eclipse.equinox.p2.engine.spi.ProvisioningAction;
20 import org.eclipse.osgi.util.NLS;
21 
22 public class PhaseSet implements IPhaseSet {
23 
24 	private final Phase[] phases;
25 	private boolean isRunning = false;
26 	private boolean isPaused = false;
27 
PhaseSet(Phase[] phases)28 	public PhaseSet(Phase[] phases) {
29 		if (phases == null)
30 			throw new IllegalArgumentException(Messages.null_phases);
31 
32 		this.phases = phases;
33 	}
34 
perform(EngineSession session, Operand[] operands, IProgressMonitor monitor)35 	public final MultiStatus perform(EngineSession session, Operand[] operands, IProgressMonitor monitor) {
36 		MultiStatus status = new MultiStatus(EngineActivator.ID, IStatus.OK, null, null);
37 		int[] weights = getProgressWeights(operands);
38 		int totalWork = getTotalWork(weights);
39 		SubMonitor pm = SubMonitor.convert(monitor, totalWork);
40 		try {
41 			isRunning = true;
42 			for (int i = 0; i < phases.length; i++) {
43 				if (pm.isCanceled()) {
44 					status.add(Status.CANCEL_STATUS);
45 					return status;
46 				}
47 				Phase phase = phases[i];
48 				phase.actionManager = session.getAgent().getService(ActionManager.class);
49 				try {
50 					phase.perform(status, session, operands, pm.newChild(weights[i]));
51 				} catch (OperationCanceledException e) {
52 					// propagate operation cancellation
53 					status.add(new Status(IStatus.CANCEL, EngineActivator.ID, e.getMessage(), e));
54 				} catch (RuntimeException e) {
55 					// "perform" calls user code and might throw an unchecked exception
56 					// we catch the error here to gather information on where the problem occurred.
57 					status.add(new Status(IStatus.ERROR, EngineActivator.ID, e.getMessage(), e));
58 				} catch (LinkageError e) {
59 					// Catch linkage errors as these are generally recoverable but let other Errors propagate (see bug 222001)
60 					status.add(new Status(IStatus.ERROR, EngineActivator.ID, e.getMessage(), e));
61 				} finally {
62 					phase.actionManager = null;
63 				}
64 				if (status.matches(IStatus.CANCEL)) {
65 					MultiStatus result = new MultiStatus(EngineActivator.ID, IStatus.CANCEL, Messages.Engine_Operation_Canceled_By_User, null);
66 					result.merge(status);
67 					return result;
68 				} else if (status.matches(IStatus.ERROR)) {
69 					MultiStatus result = new MultiStatus(EngineActivator.ID, IStatus.ERROR, phase.getProblemMessage(), null);
70 					result.add(new Status(IStatus.ERROR, EngineActivator.ID, session.getContextString(), null));
71 					result.merge(status);
72 					return result;
73 				}
74 			}
75 		} finally {
76 			pm.done();
77 			isRunning = false;
78 		}
79 		return status;
80 	}
81 
pause()82 	public synchronized boolean pause() {
83 		if (isRunning && !isPaused) {
84 			isPaused = true;
85 			for (Phase phase : phases) {
86 				phase.setPaused(isPaused);
87 			}
88 			return true;
89 		}
90 		return false;
91 	}
92 
resume()93 	public synchronized boolean resume() {
94 		if (isRunning && isPaused) {
95 			isPaused = false;
96 			for (Phase phase : phases) {
97 				phase.setPaused(isPaused);
98 			}
99 			return true;
100 		}
101 		return false;
102 	}
103 
validate(ActionManager actionManager, IProfile profile, Operand[] operands, ProvisioningContext context, IProgressMonitor monitor)104 	public final IStatus validate(ActionManager actionManager, IProfile profile, Operand[] operands, ProvisioningContext context, IProgressMonitor monitor) {
105 		Set<MissingAction> missingActions = new HashSet<>();
106 		for (Phase phase2 : phases) {
107 			Phase phase = phase2;
108 			phase.actionManager = actionManager;
109 			try {
110 				for (Operand operand : operands) {
111 					try {
112 						if (!phase.isApplicable(operand))
113 							continue;
114 
115 						List<ProvisioningAction> actions = phase.getActions(operand);
116 						if (actions == null)
117 							continue;
118 						for (ProvisioningAction action : actions) {
119 							if (action instanceof MissingAction)
120 								missingActions.add((MissingAction) action);
121 						}
122 					} catch (RuntimeException e) {
123 						// "perform" calls user code and might throw an unchecked exception
124 						// we catch the error here to gather information on where the problem occurred.
125 						return new Status(IStatus.ERROR, EngineActivator.ID, e.getMessage() + " " + getContextString(profile, phase, operand), e); //$NON-NLS-1$
126 					} catch (LinkageError e) {
127 						// Catch linkage errors as these are generally recoverable but let other Errors propagate (see bug 222001)
128 						return new Status(IStatus.ERROR, EngineActivator.ID, e.getMessage() + " " + getContextString(profile, phase, operand), e); //$NON-NLS-1$
129 					}
130 				}
131 			} finally {
132 				phase.actionManager = null;
133 			}
134 		}
135 		if (!missingActions.isEmpty()) {
136 			MissingAction[] missingActionsArray = missingActions.toArray(new MissingAction[missingActions.size()]);
137 			MissingActionsException exception = new MissingActionsException(missingActionsArray);
138 			return (new Status(IStatus.ERROR, EngineActivator.ID, exception.getMessage(), exception));
139 		}
140 		return Status.OK_STATUS;
141 	}
142 
getContextString(IProfile profile, Phase phase, Operand operand)143 	private String getContextString(IProfile profile, Phase phase, Operand operand) {
144 		return NLS.bind(Messages.session_context, new Object[] {profile.getProfileId(), phase.getClass().getName(), operand.toString(), ""}); //$NON-NLS-1$
145 	}
146 
getTotalWork(int[] weights)147 	private int getTotalWork(int[] weights) {
148 		int sum = 0;
149 		for (int weight : weights)
150 			sum += weight;
151 		return sum;
152 	}
153 
getProgressWeights(Operand[] operands)154 	private int[] getProgressWeights(Operand[] operands) {
155 		int[] weights = new int[phases.length];
156 		for (int i = 0; i < phases.length; i += 1) {
157 			if (operands.length > 0)
158 				//alter weights according to the number of operands applicable to that phase
159 				weights[i] = (phases[i].weight * countApplicable(phases[i], operands) / operands.length);
160 			else
161 				weights[i] = phases[i].weight;
162 		}
163 		return weights;
164 	}
165 
countApplicable(Phase phase, Operand[] operands)166 	private int countApplicable(Phase phase, Operand[] operands) {
167 		int count = 0;
168 		for (Operand operand : operands) {
169 			if (phase.isApplicable(operand))
170 				count++;
171 		}
172 		return count;
173 	}
174 
175 	@Override
getPhaseIds()176 	public String[] getPhaseIds() {
177 		String[] ids = new String[phases.length];
178 		for (int i = 0; i < ids.length; i++) {
179 			ids[i] = phases[i].phaseId;
180 		}
181 		return ids;
182 	}
183 
getPhases()184 	public Phase[] getPhases() {
185 		return phases;
186 	}
187 }
188