1 /*******************************************************************************
2  * Copyright (c) 2000, 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.team.core;
15 
16 import java.net.URI;
17 import java.util.ArrayList;
18 import java.util.HashSet;
19 import java.util.List;
20 import java.util.Set;
21 
22 import org.eclipse.core.filesystem.EFS;
23 import org.eclipse.core.filesystem.URIUtil;
24 import org.eclipse.core.resources.IFile;
25 import org.eclipse.core.resources.IFileModificationValidator;
26 import org.eclipse.core.resources.IProject;
27 import org.eclipse.core.resources.IProjectDescription;
28 import org.eclipse.core.resources.IProjectNature;
29 import org.eclipse.core.resources.IProjectNatureDescriptor;
30 import org.eclipse.core.resources.IResource;
31 import org.eclipse.core.resources.IResourceRuleFactory;
32 import org.eclipse.core.resources.IResourceStatus;
33 import org.eclipse.core.resources.IWorkspace;
34 import org.eclipse.core.resources.ResourcesPlugin;
35 import org.eclipse.core.resources.team.FileModificationValidationContext;
36 import org.eclipse.core.resources.team.FileModificationValidator;
37 import org.eclipse.core.resources.team.IMoveDeleteHook;
38 import org.eclipse.core.runtime.CoreException;
39 import org.eclipse.core.runtime.IAdaptable;
40 import org.eclipse.core.runtime.IConfigurationElement;
41 import org.eclipse.core.runtime.IExtension;
42 import org.eclipse.core.runtime.IExtensionPoint;
43 import org.eclipse.core.runtime.IPath;
44 import org.eclipse.core.runtime.IStatus;
45 import org.eclipse.core.runtime.Platform;
46 import org.eclipse.core.runtime.Status;
47 import org.eclipse.core.runtime.jobs.ILock;
48 import org.eclipse.core.runtime.jobs.ISchedulingRule;
49 import org.eclipse.core.runtime.jobs.Job;
50 import org.eclipse.osgi.util.NLS;
51 import org.eclipse.team.core.history.IFileHistoryProvider;
52 import org.eclipse.team.core.subscribers.Subscriber;
53 import org.eclipse.team.internal.core.Messages;
54 import org.eclipse.team.internal.core.PessimisticResourceRuleFactory;
55 import org.eclipse.team.internal.core.RepositoryProviderManager;
56 import org.eclipse.team.internal.core.TeamHookDispatcher;
57 import org.eclipse.team.internal.core.TeamPlugin;
58 
59 /**
60  * A concrete subclass of <code>RepositoryProvider</code> is created for each
61  * project that is associated with a repository provider. The lifecycle of these
62  * instances is is similar to that of the platform's 'nature' mechanism.
63  * <p>
64  * To create a repository provider and have it registered with the platform, a
65  * client must minimally:
66  * </p>
67  * <ol>
68  * <li>extend <code>RepositoryProvider</code>
69  * <li>define a repository extension in <code>plugin.xml</code>. Here is an
70  * example extension point definition:
71  *
72  * <code>
73  *	<br>&lt;extension point="org.eclipse.team.core.repository"&gt;
74  *  <br>&nbsp;&lt;repository
75  *  <br>&nbsp;&nbsp;class="org.eclipse.myprovider.MyRepositoryProvider"
76  *  <br>&nbsp;&nbsp;id="org.eclipse.myprovider.myProviderID"&gt;
77  *  <br>&nbsp;&lt;/repository&gt;
78  *	<br>&lt;/extension&gt;
79  *  </code>
80  * </ol>
81  * <p>
82  * Once a repository provider is registered with Team, then you can associate a
83  * repository provider with a project by invoking
84  * <code>RepositoryProvider.map()</code>.
85  * </p>
86  *
87  * @see RepositoryProvider#map(IProject, String)
88  *
89  * @since 2.0
90  */
91 public abstract class RepositoryProvider implements IProjectNature, IAdaptable {
92 
93 	private final static String TEAM_SETID = "org.eclipse.team.repository-provider"; //$NON-NLS-1$
94 
95 	private final static List<String> AllProviderTypeIds = initializeAllProviderTypes();
96 
97 	// the project instance that this nature is assigned to
98 	private IProject project;
99 
100 	// lock to ensure that map/unmap and getProvider support concurrency
101 	private static final ILock mappingLock = Job.getJobManager().newLock();
102 
103 	// Session property used to identify projects that are not mapped
104 	private static final Object NOT_MAPPED = new Object();
105 
106 	/**
107 	 * Instantiate a new RepositoryProvider with concrete class by given providerID
108 	 * and associate it with project.
109 	 *
110 	 * @param project the project to be mapped
111 	 * @param id the ID of the provider to be mapped to the project
112 	 * @throws TeamException if
113 	 * <ul>
114 	 * <li>There is no provider by that ID.</li>
115 	 * <li>The project is already associated with a repository provider and that provider
116 	 * prevented its unmapping.</li>
117 	 * </ul>
118 	 * @see RepositoryProvider#unmap(IProject)
119 	 */
map(IProject project, String id)120 	public static void map(IProject project, String id) throws TeamException {
121 		ISchedulingRule rule = ResourcesPlugin.getWorkspace().getRuleFactory().modifyRule(project);
122 		try {
123 			// Obtain a scheduling rule on the project before obtaining the
124 			// mappingLock. This is required because a caller of getProvider
125 			// may hold a scheduling rule before getProvider is invoked but
126 			// getProvider itself does not (and can not) obtain a scheduling rule.
127 			// Thus, the locking order is always scheduling rule followed by
128 			// mappingLock.
129 			Job.getJobManager().beginRule(rule, null);
130 			try {
131 				mappingLock.acquire();
132 				RepositoryProvider existingProvider = null;
133 
134 				if(project.getPersistentProperty(TeamPlugin.PROVIDER_PROP_KEY) != null)
135 					existingProvider = getProvider(project);	// get the real one, not the nature one
136 
137 				//if we already have a provider, and its the same ID, we're ok
138 				//if the ID's differ, unmap the existing.
139 				if(existingProvider != null) {
140 					if(existingProvider.getID().equals(id))
141 						return;	//nothing to do
142 					else
143 						unmap(project);
144 				}
145 
146 				// Create the provider as a session property before adding the persistent
147 				// property to ensure that the provider can be instantiated
148 				RepositoryProvider provider = mapNewProvider(project, id);
149 
150 				//mark it with the persistent ID for filtering
151 				try {
152 					project.setPersistentProperty(TeamPlugin.PROVIDER_PROP_KEY, id);
153 				} catch (CoreException outer) {
154 					// couldn't set the persistent property so clear the session property
155 					try {
156 						project.setSessionProperty(TeamPlugin.PROVIDER_PROP_KEY, null);
157 					} catch (CoreException inner) {
158 						// something is seriously wrong
159 						TeamPlugin.log(IStatus.ERROR, NLS.bind(Messages.RepositoryProvider_couldNotClearAfterError, new String[] { project.getName(), id }), inner);
160 					}
161 					throw outer;
162 				}
163 
164 				provider.configure();
165 
166 				//adding the nature would've caused project description delta, so trigger one
167 				project.touch(null);
168 
169 				// Set the rule factory for the provider after the touch
170 				// so the touch does not fail due to incompatible modify rules
171 				TeamHookDispatcher.setProviderRuleFactory(project, provider.getRuleFactory());
172 
173 				// Notify any listeners
174 				RepositoryProviderManager.getInstance().providerMapped(provider);
175 			} finally {
176 				mappingLock.release();
177 			}
178 		} catch (CoreException e) {
179 			throw TeamPlugin.wrapException(e);
180 		} finally {
181 			Job.getJobManager().endRule(rule);
182 		}
183 	}
184 
185 	/*
186 	 * Instantiate the provider denoted by ID and store it in the session property.
187 	 * Return the new provider instance. If a TeamException is thrown, it is
188 	 * guaranteed that the session property will not be set.
189 	 *
190 	 * @param project
191 	 * @param id
192 	 * @return RepositoryProvider
193 	 * @throws TeamException we can't instantiate the provider, or if the set
194 	 * session property fails from core
195 	 */
mapNewProvider(final IProject project, final String id)196 	private static RepositoryProvider mapNewProvider(final IProject project, final String id) throws TeamException {
197 		final RepositoryProvider provider = newProvider(id); 	// instantiate via extension point
198 
199 		if(provider == null)
200 			throw new TeamException(NLS.bind(Messages.RepositoryProvider_couldNotInstantiateProvider, new String[] { project.getName(), id }));
201 
202 		// validate that either the provider supports linked resources or the project has no linked resources
203 		if (!provider.canHandleLinkedResourceURI()) {
204 			try {
205 				project.accept(proxy -> {
206 					if (proxy.isLinked()) {
207 						if (!provider.canHandleLinkedResources() ||
208 								proxy.requestFullPath().segmentCount() > 2 ||
209 								!EFS.SCHEME_FILE.equals(proxy.requestResource().getLocationURI().getScheme()))
210 							throw new TeamException(new Status(IStatus.ERROR, TeamPlugin.ID, IResourceStatus.LINKING_NOT_ALLOWED, NLS.bind(Messages.RepositoryProvider_linkedURIsExist, new String[] { project.getName(), id }), null));
211 					}
212 					return true;
213 				}, IResource.NONE);
214 			} catch (CoreException e) {
215 				if (e instanceof TeamException) {
216 					TeamException te = (TeamException) e;
217 					throw te;
218 				}
219 				throw new TeamException(e);
220 			}
221 		}
222 		if (!provider.canHandleLinkedResources()) {
223 			try {
224 				IResource[] members = project.members();
225 				for (IResource resource : members) {
226 					if (resource.isLinked()) {
227 						throw new TeamException(new Status(IStatus.ERROR, TeamPlugin.ID, IResourceStatus.LINKING_NOT_ALLOWED, NLS.bind(Messages.RepositoryProvider_linkedResourcesExist, new String[] { project.getName(), id }), null));
228 					}
229 				}
230 			} catch (CoreException e) {
231 				throw TeamPlugin.wrapException(e);
232 			}
233 		}
234 
235 		//store provider instance as session property
236 		try {
237 			project.setSessionProperty(TeamPlugin.PROVIDER_PROP_KEY, provider);
238 			provider.setProject(project);
239 		} catch (CoreException e) {
240 			throw TeamPlugin.wrapException(e);
241 		}
242 		return provider;
243 	}
244 
mapExistingProvider(IProject project, String id)245 	private static RepositoryProvider mapExistingProvider(IProject project, String id) throws TeamException {
246 		try {
247 			// Obtain the mapping lock before creating the instance so we can make sure
248 			// that a disconnect is not happening at the same time
249 			mappingLock.acquire();
250 			try {
251 				// Ensure that the persistent property is still set
252 				// (i.e. an unmap may have come in since we checked it last
253 				String currentId = project.getPersistentProperty(TeamPlugin.PROVIDER_PROP_KEY);
254 				if (currentId == null) {
255 					// The provider has been unmapped
256 					return null;
257 				}
258 				if (!currentId.equals(id)) {
259 					// A provider has been disconnected and another connected
260 					// Since mapping creates the session property, we
261 					// can just return it
262 					return lookupProviderProp(project);
263 				}
264 			} catch (CoreException e) {
265 				throw TeamPlugin.wrapException(e);
266 			}
267 			return mapNewProvider(project, id);
268 		} finally {
269 			mappingLock.release();
270 		}
271 	}
272 	/**
273 	 * Disassociates project with the repository provider its currently mapped to.
274 	 * @param project project to unmap
275 	 * @throws TeamException The project isn't associated with any repository provider.
276 	 */
unmap(IProject project)277 	public static void unmap(IProject project) throws TeamException {
278 		ISchedulingRule rule = ResourcesPlugin.getWorkspace().getRuleFactory().modifyRule(project);
279 		try{
280 			// See the map(IProject, String) method for a description of lock ordering
281 			Job.getJobManager().beginRule(rule, null);
282 			try {
283 				mappingLock.acquire();
284 				String id = project.getPersistentProperty(TeamPlugin.PROVIDER_PROP_KEY);
285 
286 				//If you tried to remove a non-existant nature it would fail, so we need to as well with the persistent prop
287 				if(id == null) {
288 					throw new TeamException(NLS.bind(Messages.RepositoryProvider_No_Provider_Registered, new String[] { project.getName() }));
289 				}
290 
291 				//This will instantiate one if it didn't already exist,
292 				//which is ok since we need to call deconfigure() on it for proper lifecycle
293 				RepositoryProvider provider = getProvider(project);
294 				if (provider == null) {
295 					// There is a persistent property but the provider cannot be obtained.
296 					// The reason could be that the provider's plugin is no longer available.
297 					// Better log it just in case this is unexpected.
298 					TeamPlugin.log(IStatus.ERROR, NLS.bind(Messages.RepositoryProvider_couldNotInstantiateProvider, new String[] { project.getName(), id }), null);
299 				}
300 
301 				if (provider != null) provider.deconfigure();
302 
303 				project.setSessionProperty(TeamPlugin.PROVIDER_PROP_KEY, null);
304 				project.setPersistentProperty(TeamPlugin.PROVIDER_PROP_KEY, null);
305 
306 				if (provider != null) provider.deconfigured();
307 
308 				//removing the nature would've caused project description delta, so trigger one
309 				project.touch(null);
310 
311 				// Change the rule factory after the touch in order to
312 				// avoid rule incompatibility
313 				TeamHookDispatcher.setProviderRuleFactory(project, null);
314 
315 				// Notify any listeners
316 				RepositoryProviderManager.getInstance().providerUnmapped(project);
317 			} finally {
318 				mappingLock.release();
319 			}
320 		} catch (CoreException e) {
321 			throw TeamPlugin.wrapException(e);
322 		} finally {
323 			Job.getJobManager().endRule(rule);
324 		}
325 	}
326 
327 	/*
328 	 * Return the provider mapped to project, or null if none;
329 	 */
lookupProviderProp(IProject project)330 	private static RepositoryProvider lookupProviderProp(IProject project) throws CoreException {
331 		Object provider = project.getSessionProperty(TeamPlugin.PROVIDER_PROP_KEY);
332 		if (provider instanceof RepositoryProvider) {
333 			return (RepositoryProvider) provider;
334 		}
335 		return null;
336 	}
337 
338 
339 	/**
340 	 * Default constructor required for the resources plugin to instantiate this class from
341 	 * the nature extension definition.
342 	 */
RepositoryProvider()343 	public RepositoryProvider() {
344 	}
345 
346 	/**
347 	 * Configures the provider for the given project. This method is called after <code>setProject</code>.
348 	 * If an exception is generated during configuration
349 	 * of the project, the provider will not be assigned to the project.
350 	 *
351 	 * @throws CoreException if the configuration fails.
352 	 */
configureProject()353 	abstract public void configureProject() throws CoreException;
354 
355 	/**
356 	 * Configures the nature for the given project. This is called by <code>RepositoryProvider.map()</code>
357 	 * the first time a provider is mapped to a project. It is not intended to be called by clients.
358 	 *
359 	 * @throws CoreException if this method fails. If the configuration fails the provider will not be
360 	 * associated with the project.
361 	 *
362 	 * @see RepositoryProvider#configureProject()
363 	 */
364 	@Override
configure()365 	final public void configure() throws CoreException {
366 		try {
367 			configureProject();
368 		} catch(CoreException e) {
369 			try {
370 				RepositoryProvider.unmap(getProject());
371 			} catch(TeamException e2) {
372 				throw new CoreException(new Status(IStatus.ERROR, TeamPlugin.ID, 0, Messages.RepositoryProvider_Error_removing_nature_from_project___1 + getID(), e2));
373 			}
374 			throw e;
375 		}
376 	}
377 
378 	/**
379 	 * Method deconfigured is invoked after a provider has been unmaped. The
380 	 * project will no longer have the provider associated with it when this
381 	 * method is invoked. It is a last chance for the provider to clean up.
382 	 */
deconfigured()383 	protected void deconfigured() {
384 	}
385 
386 	/**
387 	 * Answer the id of this provider instance. The id should be the repository provider's
388 	 * id as defined in the provider plugin's plugin.xml.
389 	 *
390 	 * @return the nature id of this provider
391 	 */
getID()392 	abstract public String getID();
393 
394 	/**
395 	 * Returns an <code>IFileModificationValidator</code> for pre-checking operations
396 	 * that modify the contents of files.
397 	 * Returns <code>null</code> if the provider does not wish to participate in
398 	 * file modification validation.
399 	 * @return an <code>IFileModificationValidator</code> for pre-checking operations
400 	 * that modify the contents of files
401 	 *
402 	 * @see org.eclipse.core.resources.IFileModificationValidator
403 	 * @deprecated use {@link #getFileModificationValidator2()}
404 	 */
405 	@Deprecated
getFileModificationValidator()406 	public IFileModificationValidator getFileModificationValidator() {
407 		return null;
408 	}
409 
410 	/**
411 	 * Returns a {@link FileModificationValidator} for pre-checking operations
412 	 * that modify the contents of files. Returns <code>null</code> if the
413 	 * provider does not wish to participate in file modification validation. By
414 	 * default, this method wraps the old validator returned from
415 	 * {@link #getFileModificationValidator()}. Subclasses that which to remain
416 	 * backwards compatible while providing this new API should override
417 	 * {@link #getFileModificationValidator2()} to return a subclass of
418 	 * {@link FileModificationValidator} and should return the same
419 	 * validator from {@link #getFileModificationValidator()}.
420 	 * <p>
421 	 * This method is not intended to be called by clients. Clients should
422 	 * use the {@link IWorkspace#validateEdit(IFile[], Object)} method instead.
423 	 *
424 	 * @return an <code>FileModificationValidator</code> for pre-checking
425 	 *         operations that modify the contents of files
426 	 *
427 	 * @see FileModificationValidator
428 	 * @see IWorkspace#validateEdit(IFile[], Object)
429 	 * @since 3.3
430 	 */
getFileModificationValidator2()431 	public FileModificationValidator getFileModificationValidator2() {
432 		final IFileModificationValidator fileModificationValidator = getFileModificationValidator();
433 		if (fileModificationValidator == null)
434 			return null;
435 		return new FileModificationValidator() {
436 			@Override
437 			public IStatus validateSave(IFile file) {
438 				return fileModificationValidator.validateSave(file);
439 			}
440 			@Override
441 			public IStatus validateEdit(IFile[] files,
442 					FileModificationValidationContext context) {
443 				// Extract the shell from the context in order to invoke the old API
444 				Object shell;
445 				if (context == null)
446 					shell = null;
447 				else
448 					shell = context.getShell();
449 				return fileModificationValidator.validateEdit(files, shell);
450 			}
451 		};
452 	}
453 
454 	/**
455 	 * Returns an <code>IFileHistoryProvider</code> which can be used to access
456 	 * file histories. By default, returns <code>null</code>. Subclasses may override.
457 	 * @return an <code>IFileHistoryProvider</code> which can be used to access
458 	 * file histories.
459 	 * @since 3.2
460 	 */
461 	public IFileHistoryProvider getFileHistoryProvider(){
462 		return null;
463 	}
464 
465 	/**
466 	 * Returns an <code>IMoveDeleteHook</code> for handling moves and deletes
467 	 * that occur within projects managed by the provider. This allows providers
468 	 * to control how moves and deletes occur and includes the ability to prevent them.
469 	 * <p>
470 	 * Returning <code>null</code> signals that the default move and delete behavior is desired.
471 	 * @return an <code>IMoveDeleteHook</code> for handling moves and deletes
472 	 * that occur within projects managed by the provider
473 	 *
474 	 * @see org.eclipse.core.resources.team.IMoveDeleteHook
475 	 */
476 	public IMoveDeleteHook getMoveDeleteHook() {
477 		return null;
478 	}
479 
480 	/**
481 	 * Returns a brief description of this provider. The exact details of the
482 	 * representation are unspecified and subject to change, but the following
483 	 * may be regarded as typical:
484 	 *
485 	 * "SampleProject:org.eclipse.team.cvs.provider"
486 	 *
487 	 * @return a string description of this provider
488 	 */
489 	@Override
490 	public String toString() {
491 		return NLS.bind(Messages.RepositoryProvider_toString, new String[] { getProject().getName(), getID() });
492 	}
493 
494 	/**
495 	 * Returns all known (registered) RepositoryProvider ids.
496 	 *
497 	 * @return an array of registered repository provider ids.
498 	 */
499 	final public static String[] getAllProviderTypeIds() {
500 		IProjectNatureDescriptor[] desc = ResourcesPlugin.getWorkspace().getNatureDescriptors();
501 		Set<String> teamSet = new HashSet<>();
502 
503 		teamSet.addAll(AllProviderTypeIds);	// add in all the ones we know via extension point
504 		//fall back to old method of nature ID to find any for backwards compatibility
505 		for (IProjectNatureDescriptor d : desc) {
506 			String[] setIds = d.getNatureSetIds();
507 			for (String setId : setIds) {
508 				if (setId.equals(TEAM_SETID)) {
509 					teamSet.add(d.getNatureId());
510 				}
511 			}
512 		}
513 		return teamSet.toArray(new String[teamSet.size()]);
514 	}
515 
516 	/**
517 	 * Returns the provider for a given IProject or <code>null</code> if a provider is not associated with
518 	 * the project or if the project is closed or does not exist. This method should be called if the caller
519 	 * is looking for <b>any</b> repository provider. Otherwise call <code>getProvider(project, id)</code>
520 	 * to look for a specific repository provider type.
521 	 * @param project the project to query for a provider
522 	 * @return the repository provider associated with the project
523 	 */
524 	final public static RepositoryProvider getProvider(IProject project) {
525 		try {
526 			if (project.isAccessible()) {
527 
528 				//-----------------------------
529 				//First, look for the session property
530 				RepositoryProvider provider = lookupProviderProp(project);
531 				if(provider != null)
532 					return provider;
533 				// Do a quick check to see it the project is known to be unshared.
534 				// This is done to avoid accessing the persistent property store
535 				if (isMarkedAsUnshared(project))
536 					return null;
537 
538 				// -----------------------------
539 				//Next, check if it has the ID as a persistent property, if yes then instantiate provider
540 				String id = project.getPersistentProperty(TeamPlugin.PROVIDER_PROP_KEY);
541 				if(id != null)
542 					return mapExistingProvider(project, id);
543 
544 				//Couldn't find using new method, fall back to lookup using natures for backwards compatibility
545 				//-----------------------------
546 				IProjectDescription projectDesc = project.getDescription();
547 				String[] natureIds = projectDesc.getNatureIds();
548 				IWorkspace workspace = ResourcesPlugin.getWorkspace();
549 				// for every nature id on this project, find it's natures sets and check if it is
550 				// in the team set.
551 				for (String natureId : natureIds) {
552 					IProjectNatureDescriptor desc = workspace.getNatureDescriptor(natureId);
553 					// The descriptor can be null if the nature doesn't exist
554 					if (desc != null) {
555 						String[] setIds = desc.getNatureSetIds();
556 						for (String setId : setIds) {
557 							if (setId.equals(TEAM_SETID)) {
558 								return getProvider(project, natureId);
559 							}
560 						}
561 					}
562 				}
563 				markAsUnshared(project);
564 			}
565 		} catch(CoreException e) {
566 			if (!isAcceptableException(e)) {
567 				TeamPlugin.log(e);
568 			}
569 			markAsUnshared(project);
570 		}
571 		return null;
572 	}
573 
574 	/*
575 	 * Return whether the given exception is acceptable during a getProvider().
576 	 * If the exception is acceptable, it is assumed that there is no provider
577 	 * on the project.
578 	 */
579 	private static boolean isAcceptableException(CoreException e) {
580 		return e.getStatus().getCode() == IResourceStatus.RESOURCE_NOT_FOUND;
581 	}
582 
583 	/**
584 	 * Returns a provider of type with the given id if associated with the given project
585 	 * or <code>null</code> if the project is not associated with a provider of that type
586 	 * or the nature id is that of a non-team repository provider nature.
587 	 *
588 	 * @param project the project to query for a provider
589 	 * @param id the repository provider id
590 	 * @return the repository provider
591 	 */
592 	final public static RepositoryProvider getProvider(IProject project, String id) {
593 		try {
594 			if (project.isAccessible()) {
595 				// Look for an existing provider first to avoid accessing persistent properties
596 				RepositoryProvider provider = lookupProviderProp(project);  //throws core, we will reuse the catching already here
597 				if(provider != null) {
598 					if (provider.getID().equals(id)) {
599 						return provider;
600 					} else {
601 						return null;
602 					}
603 				}
604 				// Do a quick check to see it the project is known to be unshared.
605 				// This is done to avoid accessing the persistent property store
606 				if (isMarkedAsUnshared(project))
607 					return null;
608 
609 				// There isn't one so check the persistent property
610 				String existingID = project.getPersistentProperty(TeamPlugin.PROVIDER_PROP_KEY);
611 				if(id.equals(existingID)) {
612 					// The ids are equal so instantiate and return
613 					RepositoryProvider newProvider = mapExistingProvider(project, id);
614 					if (newProvider!= null && newProvider.getID().equals(id)) {
615 						return newProvider;
616 					} else {
617 						// The id changed before we could create the desired provider
618 						return null;
619 					}
620 				}
621 
622 				//couldn't find using new method, fall back to lookup using natures for backwards compatibility
623 				//-----------------------------
624 
625 				// if the nature id given is not in the team set then return
626 				// null.
627 				IProjectNatureDescriptor desc = ResourcesPlugin.getWorkspace().getNatureDescriptor(id);
628 				if(desc == null) //for backwards compatibility, may not have any nature by that ID
629 					return null;
630 
631 				String[] setIds = desc.getNatureSetIds();
632 				for (String setId : setIds) {
633 					if (setId.equals(TEAM_SETID)) {
634 						return (RepositoryProvider)project.getNature(id);
635 					}
636 				}
637 				markAsUnshared(project);
638 			}
639 		} catch(CoreException e) {
640 			if (!isAcceptableException(e)) {
641 				TeamPlugin.log(e);
642 			}
643 			markAsUnshared(project);
644 		}
645 		return null;
646 	}
647 
648 	/**
649 	 * Returns whether the given project is shared or not. This is a lightweight
650 	 * method in that it will not instantiate a provider instance (as
651 	 * <code>getProvider</code> would) if one is not already instantiated.
652 	 *
653 	 * Note that IProject.touch() generates a project description delta.  This, in combination
654 	 * with isShared() can be used to be notified of sharing/unsharing of projects.
655 	 *
656 	 * @param project the project being tested.
657 	 * @return boolean
658 	 *
659 	 * @see #getProvider(IProject)
660 	 *
661 	 * @since 2.1
662 	 */
663 	public static boolean isShared(IProject project) {
664 		if (!project.isAccessible()) return false;
665 		try {
666 			if (lookupProviderProp(project) != null) return true;
667 			// Do a quick check to see it the project is known to be unshared.
668 			// This is done to avoid accessing the persistent property store
669 			if (isMarkedAsUnshared(project))
670 				return false;
671 			boolean shared = project.getPersistentProperty(TeamPlugin.PROVIDER_PROP_KEY) != null;
672 			if (!shared)
673 				markAsUnshared(project);
674 			return shared;
675 		} catch (CoreException e) {
676 			TeamPlugin.log(e);
677 			return false;
678 		}
679 	}
680 
681 	private static boolean isMarkedAsUnshared(IProject project) {
682 		try {
683 			return project.getSessionProperty(TeamPlugin.PROVIDER_PROP_KEY) == NOT_MAPPED;
684 		} catch (CoreException e) {
685 			return false;
686 		}
687 	}
688 
689 	private static void markAsUnshared(IProject project) {
690 		try {
691 			project.setSessionProperty(TeamPlugin.PROVIDER_PROP_KEY, NOT_MAPPED);
692 		} catch (CoreException e) {
693 			// Just ignore the error as this is just an optimization
694 		}
695 	}
696 
697 	@Override
698 	public IProject getProject() {
699 		return project;
700 	}
701 
702 	@Override
703 	public void setProject(IProject project) {
704 		this.project = project;
705 	}
706 
707 	private static List<String> initializeAllProviderTypes() {
708 		List<String> allIDs = new ArrayList<>();
709 
710 		TeamPlugin plugin = TeamPlugin.getPlugin();
711 		if (plugin != null) {
712 			IExtensionPoint extension = Platform.getExtensionRegistry().getExtensionPoint(TeamPlugin.ID, TeamPlugin.REPOSITORY_EXTENSION);
713 			if (extension != null) {
714 				IExtension[] extensions =  extension.getExtensions();
715 				for (IExtension e : extensions) {
716 					IConfigurationElement[] configElements = e.getConfigurationElements();
717 					for (IConfigurationElement configElement : configElements) {
718 						String extensionId = configElement.getAttribute("id"); //$NON-NLS-1$
719 						allIDs.add(extensionId);
720 					}
721 				}
722 			}
723 		}
724 		return allIDs;
725 	}
726 
727 	private static RepositoryProvider newProvider(String id) {
728 		TeamPlugin plugin = TeamPlugin.getPlugin();
729 		if (plugin != null) {
730 			IExtensionPoint extension = Platform.getExtensionRegistry().getExtensionPoint(TeamPlugin.ID, TeamPlugin.REPOSITORY_EXTENSION);
731 			if (extension != null) {
732 				IExtension[] extensions =  extension.getExtensions();
733 				for (IExtension ext : extensions) {
734 					IConfigurationElement[] configElements = ext.getConfigurationElements();
735 					for (IConfigurationElement configElement : configElements) {
736 						String extensionId = configElement.getAttribute("id"); //$NON-NLS-1$
737 						if (extensionId != null && extensionId.equals(id)) {
738 							try {
739 								return (RepositoryProvider) configElement.createExecutableExtension("class"); //$NON-NLS-1$
740 							} catch (CoreException e) {
741 								TeamPlugin.log(e);
742 							} catch (ClassCastException e) {
743 								String className = configElement.getAttribute("class"); //$NON-NLS-1$
744 								TeamPlugin.log(IStatus.ERROR, NLS.bind(Messages.RepositoryProvider_invalidClass, new String[] { id, className }), e);
745 							}
746 							return null;
747 						}
748 					}
749 				}
750 			}
751 		}
752 		return null;
753 	}
754 
755 	/**
756 	 * Method validateCreateLink is invoked by the Platform Core TeamHook when a
757 	 * linked resource is about to be added to the provider's project. It should
758 	 * not be called by other clients and it should not need to be overridden by
759 	 * subclasses (although it is possible to do so in special cases).
760 	 * Subclasses can indicate that they support linked resources by overriding
761 	 * the <code>canHandleLinkedResources()</code> method.
762 	 *
763 	 * @param resource see <code>org.eclipse.core.resources.team.TeamHook</code>
764 	 * @param updateFlags see <code>org.eclipse.core.resources.team.TeamHook</code>
765 	 * @param location see <code>org.eclipse.core.resources.team.TeamHook</code>
766 	 * @return IStatus see <code>org.eclipse.core.resources.team.TeamHook</code>
767 	 *
768 	 * @see RepositoryProvider#canHandleLinkedResources()
769 	 *
770 	 * @deprecated see {@link #validateCreateLink(IResource, int, URI) } instead
771 	 * @since 2.1
772 	 */
773 	@Deprecated
774 	public IStatus validateCreateLink(IResource resource, int updateFlags, IPath location) {
775 		if (canHandleLinkedResources()) {
776 			return Team.OK_STATUS;
777 		} else {
778 			return new Status(IStatus.ERROR, TeamPlugin.ID, IResourceStatus.LINKING_NOT_ALLOWED, NLS.bind(Messages.RepositoryProvider_linkedResourcesNotSupported, new String[] { getProject().getName(), getID() }), null);
779 		}
780 	}
781 
782 	/**
783 	 * Method validateCreateLink is invoked by the Platform Core TeamHook when a
784 	 * linked resource is about to be added to the provider's project. It should
785 	 * not be called by other clients and it should not need to be overridden by
786 	 * subclasses (although it is possible to do so in special cases).
787 	 * Subclasses can indicate that they support linked resources by overriding
788 	 * the <code>canHandleLinkedResourcesAtArbitraryDepth()</code> method.
789 	 *
790 	 * @param resource see <code>org.eclipse.core.resources.team.TeamHook</code>
791 	 * @param updateFlags see <code>org.eclipse.core.resources.team.TeamHook</code>
792 	 * @param location see <code>org.eclipse.core.resources.team.TeamHook</code>
793 	 * @return IStatus see <code>org.eclipse.core.resources.team.TeamHook</code>
794 	 *
795 	 * @see RepositoryProvider#canHandleLinkedResourceURI()
796 	 *
797 	 * @since 3.2
798 	 */
799 	public IStatus validateCreateLink(IResource resource, int updateFlags, URI location) {
800 		if (resource.getProjectRelativePath().segmentCount() == 1 && EFS.SCHEME_FILE.equals(location.getScheme())) {
801 			// This is compatible with the old style link so invoke the old
802 			// validateLink
803 			return validateCreateLink(resource, updateFlags, URIUtil.toPath(location));
804 		}
805 		if (canHandleLinkedResourceURI()) {
806 			return Team.OK_STATUS;
807 		} else {
808 			return new Status(IStatus.ERROR, TeamPlugin.ID, IResourceStatus.LINKING_NOT_ALLOWED, NLS.bind(Messages.RepositoryProvider_linkedURIsNotSupported, new String[] { getProject().getName(), getID() }), null);
809 		}
810 	}
811 
812 	/**
813 	 * Method canHandleLinkedResources should be overridden by subclasses who
814 	 * support linked resources. At a minimum, supporting linked resources
815 	 * requires changes to the move/delete hook
816 	 * {@link org.eclipse.core.resources.team.IMoveDeleteHook}. This method is
817 	 * called after the RepositoryProvider is instantiated but before
818 	 * <code>setProject()</code> is invoked so it will not have access to any
819 	 * state determined from the <code>setProject()</code> method.
820 	 * @return boolean
821 	 *
822 	 * @see org.eclipse.core.resources.team.IMoveDeleteHook
823 	 *
824 	 * @since 2.1
825 	 *
826 	 * @deprecated see {@link #canHandleLinkedResourceURI() }
827 	 */
828 	@Deprecated
829 	public boolean canHandleLinkedResources() {
830 		return canHandleLinkedResourceURI();
831 	}
832 
833 	/**
834 	 * Return whether this repository provider can handle linked resources that
835 	 * are located via a URI (i.e. may not be on the local file system) or occur
836 	 * at an arbitrary depth in the project. This should be overridden by
837 	 * subclasses who support linked resources at arbitrary depth and/or in
838 	 * non-local file systems. This is not enabled by default since linked
839 	 * resources previously only occurred at the root of a project but now can
840 	 * occur anywhere within a project. This method is called after the
841 	 * RepositoryProvider is instantiated but before <code>setProject()</code>
842 	 * is invoked so it will not have access to any state determined from the
843 	 * <code>setProject()</code> method.
844 	 *
845 	 * @return whether this repository provider can handle linked resources that
846 	 *         are located via a URI or occur at an arbitrary depth in the
847 	 *         project
848 	 *
849 	 * @see #validateCreateLink(IResource, int, URI)
850 	 *
851 	 * @since 3.2
852 	 */
853 	public boolean canHandleLinkedResourceURI() {
854 		return false;
855 	}
856 
857 	@Override
858 	public <T> T getAdapter(Class<T> adapter) {
859 		return null;
860 	}
861 
862 	/**
863 	 * Return the resource rule factory for this provider. This factory
864 	 * will be used to determine the scheduling rules that are to be obtained
865 	 * when performing various resource operations (e.g. move, copy, delete, etc.)
866 	 * on the resources in the project the provider is mapped to.
867 	 * <p>
868 	 * By default, the factory returned by this method is pessimistic and
869 	 * obtains the workspace lock for all operations that could result in a
870 	 * callback to the provider (either through the <code>IMoveDeleteHook</code>
871 	 * or <code>IFileModificationValidator</code>). This is done to ensure that
872 	 * older providers are not broken. However, providers should override this
873 	 * method and provide a subclass of {@link org.eclipse.core.resources.team.ResourceRuleFactory}
874 	 * that provides rules of a more optimistic granularity (e.g. project
875 	 * or lower).
876 	 * @return the rule factory for this provider
877 	 * @since 3.0
878 	 * @see org.eclipse.core.resources.team.ResourceRuleFactory
879 	 */
880 	public IResourceRuleFactory getRuleFactory() {
881 		return new PessimisticResourceRuleFactory();
882 	}
883 
884 	/**
885 	 * Return a {@link Subscriber} that describes the synchronization state
886 	 * of the resources contained in the project associated with this
887 	 * provider. The subscriber is obtained from the {@link RepositoryProviderType}
888 	 * associated with a provider and is thus shared for all providers of the
889 	 * same type.
890 	 * @return a subscriber that provides resource synchronization state or <code>null</code>
891 	 * @since 3.2
892 	 */
893 	public final Subscriber getSubscriber() {
894 		RepositoryProviderType type = RepositoryProviderType.getProviderType(getID());
895 		if (type != null)
896 			return type.getSubscriber();
897 		return null;
898 	}
899 }
900