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><extension point="org.eclipse.team.core.repository"> 74 * <br> <repository 75 * <br> class="org.eclipse.myprovider.MyRepositoryProvider" 76 * <br> id="org.eclipse.myprovider.myProviderID"> 77 * <br> </repository> 78 * <br></extension> 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