1 /*******************************************************************************
2  * Copyright (c) 2000, 2010 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.team.internal.ccvs.core.client;
15 
16 import java.util.*;
17 
18 import org.eclipse.core.runtime.*;
19 import org.eclipse.osgi.util.NLS;
20 import org.eclipse.team.core.TeamException;
21 import org.eclipse.team.internal.ccvs.core.*;
22 import org.eclipse.team.internal.ccvs.core.client.listeners.ICommandOutputListener;
23 import org.eclipse.team.internal.ccvs.core.connection.CVSServerException;
24 
25 /**
26  * Abstract base class for requests that are to be sent to the server.
27  */
28 public abstract class Request {
29 	public static final ExpandModules EXPAND_MODULES = new ExpandModules();
30 	public static final ValidRequests VALID_REQUESTS = new ValidRequests();
31 
32 	/*** Response handler map ***/
33 	private static final Map<String,ResponseHandler> responseHandlers = new HashMap<>();
34 
initializeHandlerCache()35 	private static void initializeHandlerCache() {
36 		synchronized(responseHandlers) {
37 			registerResponseHandler(new CheckedInHandler());
38 			registerResponseHandler(new CopyHandler());
39 			registerResponseHandler(new ModTimeHandler());
40 			registerResponseHandler(new NewEntryHandler());
41 			registerResponseHandler(new RemovedHandler());
42 			registerResponseHandler(new RemoveEntryHandler());
43 			registerResponseHandler(new StaticHandler(true));
44 			registerResponseHandler(new StaticHandler(false));
45 			registerResponseHandler(new StickyHandler(true));
46 			registerResponseHandler(new StickyHandler(false));
47 			registerResponseHandler(new UpdatedHandler(UpdatedHandler.HANDLE_UPDATED));
48 			registerResponseHandler(new UpdatedHandler(UpdatedHandler.HANDLE_UPDATE_EXISTING));
49 			registerResponseHandler(new UpdatedHandler(UpdatedHandler.HANDLE_CREATED));
50 			registerResponseHandler(new UpdatedHandler(UpdatedHandler.HANDLE_MERGED));
51 			registerResponseHandler(new ValidRequestsHandler());
52 			registerResponseHandler(new ModuleExpansionHandler());
53 			registerResponseHandler(new MTHandler());
54 			registerResponseHandler(new NotifiedHandler());
55 			registerResponseHandler(new TemplateHandler());
56 		}
57 	}
registerResponseHandler(ResponseHandler handler)58 	private static void registerResponseHandler(ResponseHandler handler) {
59 		synchronized(responseHandlers) {
60 			responseHandlers.put(handler.getResponseID(), handler);
61 		}
62 	}
63 
64 	/**
65 	 * This method is invoked by Session to get a mutable copy of the
66 	 * global list of acceptable response handlers.
67 	 *
68 	 * @return a map of response handlers
69 	 */
getReponseHandlerMap()70 	protected static Map<String,ResponseHandler> getReponseHandlerMap() {
71 		synchronized(responseHandlers) {
72 			if (responseHandlers.isEmpty()) {
73 				initializeHandlerCache();
74 			}
75 			Map<String,ResponseHandler> copy = new HashMap<>();
76 			for (Iterator iter = responseHandlers.values().iterator(); iter.hasNext();) {
77 				ResponseHandler handler = (ResponseHandler) iter.next();
78 				copy.put(handler.getResponseID(), handler.getInstance());
79 			}
80 			return copy;
81 		}
82 	}
83 	/**
84 	 * Prevents client code from instantiating us.
85 	 */
Request()86 	protected Request() { }
87 
88 	/**
89 	 * Returns the string used to invoke this request on the server.
90 	 * [template method]
91 	 *
92 	 * @return the request identifier string
93 	 */
getRequestId()94 	protected abstract String getRequestId();
95 
96 	/**
97 	 * Executes a request and processes the responses.
98 	 *
99 	 * @param session the open CVS session
100 	 * @param listener the command output listener, or null to discard all messages
101 	 * @param monitor the progress monitor
102 	 * @return a status code indicating success or failure of the operation
103 	 */
executeRequest(Session session, ICommandOutputListener listener, IProgressMonitor monitor)104 	protected IStatus executeRequest(Session session, ICommandOutputListener listener,
105 		IProgressMonitor monitor) throws CVSException {
106 		// send request
107 		session.sendRequest(getRequestId());
108 
109 		// This number can be tweaked if the monitor is judged to move too
110 		// quickly or too slowly. After some experimentation this is a good
111 		// number for both large projects (it doesn't move so quickly as to
112 		// give a false sense of speed) and smaller projects (it actually does
113 		// move some rather than remaining still and then jumping to 100).
114 		final int TOTAL_WORK = 300;
115 		monitor.beginTask(CVSMessages.Command_receivingResponses, TOTAL_WORK);
116 		monitor.subTask(CVSMessages.Command_receivingResponses);
117 		int halfWay = TOTAL_WORK / 2;
118 		int currentIncrement = 4;
119 		int nextProgress = currentIncrement;
120 		int worked = 0;
121 
122 		// If the session is connected to a CVSNT server (1.11.1.1), we'll need to do some special handling for
123 		// some errors. Unfortunately, CVSNT 1.11.1.1 will drop the connection after so some functionality is
124 		// still affected
125 		boolean isCVSNT = session.isCVSNT();
126 
127 		session.clearErrors();
128 		for (;;) {
129 			// update monitor work amount
130 			if (--nextProgress <= 0) {
131 				monitor.worked(1);
132 				worked++;
133 				if (worked >= halfWay) {
134 					// we have passed the current halfway point, so double the
135 					// increment and reset the halfway point.
136 					currentIncrement *= 2;
137 					halfWay += (TOTAL_WORK - halfWay) / 2;
138 				}
139 				// reset the progress counter to another full increment
140 				nextProgress = currentIncrement;
141 			}
142 			Policy.checkCanceled(monitor);
143 
144 			// retrieve a response line
145 			String response = session.readLine();
146 			int spacePos = response.indexOf(' ');
147 			String argument;
148 			if (spacePos != -1) {
149 				argument = response.substring(spacePos + 1);
150 				response = response.substring(0, spacePos);
151 			} else argument = "";  //$NON-NLS-1$
152 
153 			// handle completion responses
154 			if (response.equals("ok")) {  //$NON-NLS-1$
155 				break;
156 			} else if (response.equals("error") || (isCVSNT && response.isEmpty())) {  //$NON-NLS-1$
157 				argument = argument.trim();
158 				boolean serious = false;
159 				if (argument.length() == 0) {
160 					argument = getServerErrorMessage();
161 				} else {
162 					argument = NLS.bind(CVSMessages.Command_seriousServerError, new String[] { argument });
163 					if (!session.hasErrors()) {
164 						session.addError(new CVSStatus(IStatus.ERROR, CVSStatus.SERVER_ERROR, argument,session.getLocalRoot()));
165 					}
166 					serious = true;
167 				}
168 
169 				if (!session.hasErrors()) {
170 					session.addError(new CVSStatus(IStatus.ERROR, CVSStatus.SERVER_ERROR, CVSMessages.Command_noMoreInfoAvailable,session.getLocalRoot()));
171 				}
172 				IStatus status = new MultiStatus(CVSProviderPlugin.ID, CVSStatus.SERVER_ERROR,
173 						session.getErrors(),
174 					argument, null);
175 				if (serious) {
176 					throw new CVSServerException(status);
177 				} else {
178 					// look for particularly bad errors in the accumulated statuses
179 					IStatus[] errors = session.getErrors();
180 					for (IStatus s : errors) {
181 						if (s.getCode() == CVSStatus.PROTOCOL_ERROR) {
182 							throw new CVSServerException(status);
183 						}
184 					}
185 				}
186 				return status;
187 			// handle message responses
188 			} else if (response.equals("MT")) {  //$NON-NLS-1$
189 				// Handle the MT response
190 				MTHandler handler = (MTHandler) session.getResponseHandler(response);
191 				if (handler != null) {
192 					handler.handle(session, argument, monitor);
193 				} else {
194 					throw new CVSException(new org.eclipse.core.runtime.Status(IStatus.ERROR,
195 						CVSProviderPlugin.ID, TeamException.IO_FAILED,
196 						NLS.bind(CVSMessages.Command_unsupportedResponse, new String[] { response, argument }), null));
197 				}
198 				// If a line is available, pass it on to the message listener
199 				// and console as if it were an M response
200 				if (handler.isLineAvailable()) {
201 					String line = handler.getLine();
202 					IStatus status = listener.messageLine(line, session.getCVSRepositoryLocation(), session.getLocalRoot(), monitor);
203 					session.addError(status); // The session ignores OK status
204 					ConsoleListeners.getInstance().messageLineReceived(session, line, status);
205 
206 				}
207 			} else if (response.equals("M")) {  //$NON-NLS-1$
208 				IStatus status = listener.messageLine(argument, session.getCVSRepositoryLocation(), session.getLocalRoot(), monitor);
209 				session.addError(status); // The session ignores OK status
210 				ConsoleListeners.getInstance().messageLineReceived(session, argument, status);
211 			} else if (response.equals("E")) { //$NON-NLS-1$
212 				IStatus status = listener.errorLine(argument, session.getCVSRepositoryLocation(), session.getLocalRoot(), monitor);
213 				session.addError(status); // The session ignores OK status
214 				ConsoleListeners.getInstance().errorLineReceived(session, argument, status);
215 			// handle other responses
216 			} else {
217 				ResponseHandler handler = session.getResponseHandler(response);
218 				if (handler != null) {
219 					handler.handle(session, argument, monitor);
220 				} else {
221 					throw new CVSException(new org.eclipse.core.runtime.Status(IStatus.ERROR,
222 						CVSProviderPlugin.ID, TeamException.IO_FAILED,
223 						NLS.bind(CVSMessages.Command_unsupportedResponse, new String[] { response, argument }), null));
224 				}
225 			}
226 		}
227 		if (!session.hasErrors()) {
228 			return ICommandOutputListener.OK;
229 		} else {
230 			return new MultiStatus(CVSProviderPlugin.ID, IStatus.INFO,
231 				session.getErrors(),
232 				NLS.bind(CVSMessages.Command_warnings, new String[] { getDisplayText() }), null);
233 		}
234 	}
235 
236 	/*
237 	 * Provide the message that is used for the status that is generated when the server
238 	 * reports as error.
239 	 */
getServerErrorMessage()240 	protected String getServerErrorMessage() {
241 		return NLS.bind(CVSMessages.Command_serverError, new String[] { getDisplayText() });
242 	}
getDisplayText()243 	protected String getDisplayText() {
244 		return getRequestId();
245 	}
246 }
247