Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'bundles/org.eclipse.osgi/container')
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/adaptor/EclipseStarter.java1529
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/adaptor/LocationManager.java415
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/adaptor/package.html17
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/BundleLocalizationImpl.java35
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/CachedManifest.java156
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/ClasspathManifest.java87
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/ContextFinder.java175
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/DefaultStartupMonitor.java68
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseAdaptorHook.java231
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseAppLauncher.java159
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseClassLoadingHook.java243
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseEnvironmentInfo.java243
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseErrorHandler.java113
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseLazyStarter.java281
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseLogFactory.java107
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseLogHook.java147
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseLogWriter.java726
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseStorageHook.java522
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/IModel.java92
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/IPluginInfo.java52
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/MessageHelper.java66
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/PluginConverterImpl.java761
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/PluginParser.java710
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/Semaphore.java72
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/URLConverterImpl.java55
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/BundleStats.java132
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/ClassStats.java121
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/ClassloaderStats.java242
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/ResourceBundleStats.java123
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/StatsManager.java237
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/BasicReadWriteLock.java69
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/EventAdminAdapter.java117
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/EventAdminLogListener.java153
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/ExtendedLogEntryImpl.java120
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/ExtendedLogReaderServiceFactory.java269
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/ExtendedLogReaderServiceImpl.java66
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/ExtendedLogServiceFactory.java73
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/ExtendedLogServiceImpl.java93
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/LogServiceManager.java316
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/LoggerImpl.java56
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/SerializedTaskQueue.java58
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/BaseAdaptor.java699
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/BaseData.java530
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/HookConfigurator.java25
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/HookRegistry.java334
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/BundleEntry.java98
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/BundleFile.java232
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/BundleFileWrapperChain.java86
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/DirBundleFile.java107
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/DirZipBundleEntry.java78
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/FileBundleEntry.java100
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/MRUBundleFileList.java239
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/NestedDirBundleFile.java99
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/ZipBundleEntry.java172
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/ZipBundleFile.java357
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/hooks/AdaptorHook.java100
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/hooks/BundleFileFactoryHook.java35
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/hooks/BundleFileWrapperFactoryHook.java38
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/hooks/ClassLoadingHook.java98
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/hooks/ClassLoadingStatsHook.java80
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/hooks/StorageHook.java138
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/loader/BaseClassLoader.java76
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/loader/ClasspathEntry.java96
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/loader/ClasspathManager.java737
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/loader/FragmentClasspath.java74
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/loader/ParallelClassLoader.java52
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/event/BatchBundleListener.java63
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/BundleClassLoader.java178
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/BundleData.java251
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/BundleOperation.java77
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/BundleProtectionDomain.java71
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/BundleWatcher.java90
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/ClassLoaderDelegate.java134
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/ClassLoaderDelegateHook.java124
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/EventPublisher.java37
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/FilePath.java257
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/FrameworkAdaptor.java296
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/PermissionStorage.java100
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/StatusException.java45
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/console/CommandInterpreter.java91
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/console/CommandProvider.java46
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/console/ConsoleSession.java95
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/console/package.html17
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/AbstractBundle.java1545
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/AliasMapper.java155
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleContextImpl.java974
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleFragment.java334
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleHost.java686
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleRepository.java196
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleResourceHandler.java304
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleSource.java45
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleURLConnection.java131
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/ConsoleManager.java82
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/Constants.java254
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/CoreResolverHookFactory.java216
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/EquinoxLauncher.java347
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/ExportedPackageImpl.java99
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/FilterImpl.java1751
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/Framework.java2007
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/InternalSystemBundle.java433
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/ManifestLocalization.java241
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/PackageAdminImpl.java766
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/ReferenceInputStream.java38
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/StartLevelEvent.java82
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/StartLevelManager.java678
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/SystemBundleActivator.java110
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/UniversalUniqueIdentifier.java270
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/Util.java208
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/osname.aliases46
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/processor.aliases28
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/ContentHandlerFactory.java163
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/ContentHandlerProxy.java152
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/MultiplexingContentHandler.java33
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/MultiplexingFactory.java173
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/MultiplexingURLStreamHandler.java247
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/NullURLStreamHandlerService.java74
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/ProtocolActivator.java22
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/StreamHandlerFactory.java223
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/URLStreamHandlerFactoryProxyFor15.java41
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/URLStreamHandlerProxy.java218
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/URLStreamHandlerSetter.java39
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/bundleentry/Handler.java49
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/bundleresource/Handler.java55
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/reference/Handler.java47
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/reference/ReferenceURLConnection.java121
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/util/Headers.java313
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/util/KeyedElement.java39
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/util/KeyedHashSet.java490
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/util/ObjectPool.java58
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/AdaptorMsg.java57
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/AdaptorUtil.java280
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/ArrayMap.java184
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BaseClassLoadingHook.java139
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BaseHookConfigurator.java30
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BasePermissionStorage.java97
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BaseStorage.java1450
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BaseStorageHook.java448
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BundleInstall.java132
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BundleUninstall.java74
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BundleUpdate.java151
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/DefaultClassLoader.java300
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/DevClassLoadingHook.java139
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/DevClassPathHelper.java160
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/ExternalMessages.properties35
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/InvalidVersion.java44
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/StateManager.java320
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/SystemBundleData.java219
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/weaving/DynamicImportList.java83
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/weaving/WeavingHookConfigurator.java126
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/weaving/WovenClassImpl.java228
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/BundleLoader.java1270
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/BundleLoaderProxy.java247
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/FilteredSourcePackage.java112
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/MultiSourcePackage.java67
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/NullPackageSource.java70
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/PackageSource.java81
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/SingleSourcePackage.java69
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/SystemBundleLoader.java277
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/buddy/DependentPolicy.java119
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/buddy/GlobalPolicy.java70
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/buddy/IBuddyPolicy.java22
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/buddy/PolicyHandler.java219
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/buddy/RegisteredPolicy.java121
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/buddy/SystemPolicy.java93
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/BundlePermissions.java77
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/EquinoxSecurityManager.java192
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/PermissionAdminTable.java50
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/PermissionInfoCollection.java132
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/PermissionsHash.java103
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/SecurePermissionStorage.java109
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/SecurityAdmin.java874
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/SecurityRow.java457
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/SecurityRowSnapShot.java85
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/SecurityTable.java133
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/SecurityTableUpdate.java44
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/default.permissions25
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/implied.permissions50
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/profile/DefaultProfileLogger.java391
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/profile/Profile.java268
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/profile/ProfileLogger.java42
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/service/security/AuthorizationEngine.java88
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/service/security/AuthorizationEvent.java82
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/service/security/AuthorizationListener.java27
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/service/security/AuthorizationStatus.java35
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/verifier/CertificateChain.java64
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/verifier/CertificateTrustAuthority.java37
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/verifier/CertificateVerifier.java59
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/verifier/CertificateVerifierFactory.java42
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/service/security/DefaultAuthorizationEngine.java186
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/service/security/KeyStoreTrustEngine.java335
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/FilteredServiceListener.java205
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/HookContext.java49
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ModifiedServiceEvent.java71
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ServiceProperties.java186
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ServiceReferenceImpl.java275
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ServiceRegistrationImpl.java650
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ServiceRegistry.java1362
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ServiceUse.java294
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ShrinkableCollection.java196
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ShrinkableEntrySetValueCollection.java65
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ShrinkableValueCollectionMap.java191
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/BERProcessor.java274
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/Base64.java198
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/BundleInstallListener.java40
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/DigestedInputStream.java160
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/LegacyVerifierFactory.java137
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/PKCS7DateParser.java45
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/PKCS7Processor.java484
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignatureBlockProcessor.java499
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedBundleFile.java218
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedBundleHook.java350
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentConstants.java71
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentFile.java131
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentImpl.java168
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentMessages.java51
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentMessages.properties37
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedStorageHook.java243
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignerInfoImpl.java71
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/TrustEngineListener.java167
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/launch/Equinox.java301
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/launch/EquinoxFWClassLoader.java71
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/launch/EquinoxFactory.java27
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/launch/package.html17
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/pluginconversion/PluginConversionException.java71
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/pluginconversion/PluginConverter.java80
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/BaseDescription.java97
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/BundleDelta.java126
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/BundleDescription.java271
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/BundleSpecification.java39
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/DisabledInfo.java91
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/ExportPackageDescription.java60
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/GenericDescription.java64
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/GenericSpecification.java57
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/HostSpecification.java35
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/ImportPackageSpecification.java69
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/NativeCodeDescription.java84
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/NativeCodeSpecification.java43
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/PlatformAdmin.java121
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/Resolver.java146
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/ResolverError.java194
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/ResolverHookException.java29
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/State.java638
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/StateDelta.java53
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/StateHelper.java212
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/StateObjectFactory.java487
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/StateWire.java81
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/VersionConstraint.java107
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/VersionRange.java127
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/extras/DescriptionReference.java28
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/extras/Sortable.java27
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/extras/SpecificationReference.java28
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/package.html17
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/security/TrustEngine.java146
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/security/package.html14
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/signedcontent/InvalidContentException.java59
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/signedcontent/SignedContent.java92
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/signedcontent/SignedContentEntry.java60
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/signedcontent/SignedContentFactory.java60
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/signedcontent/SignerInfo.java55
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/signedcontent/package.html14
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/util/TextProcessor.java298
-rw-r--r--bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/util/package.html17
262 files changed, 53663 insertions, 0 deletions
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/adaptor/EclipseStarter.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/adaptor/EclipseStarter.java
new file mode 100644
index 000000000..5783d5a9e
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/adaptor/EclipseStarter.java
@@ -0,0 +1,1529 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Alex Blewitt (bug 172969)
+ *******************************************************************************/
+package org.eclipse.core.runtime.adaptor;
+
+import java.io.*;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.net.*;
+import java.security.CodeSource;
+import java.security.ProtectionDomain;
+import java.util.*;
+import org.eclipse.core.runtime.internal.adaptor.*;
+import org.eclipse.osgi.framework.adaptor.*;
+import org.eclipse.osgi.framework.internal.core.*;
+import org.eclipse.osgi.framework.internal.core.Constants;
+import org.eclipse.osgi.framework.log.FrameworkLog;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+import org.eclipse.osgi.internal.baseadaptor.BaseStorageHook;
+import org.eclipse.osgi.internal.profile.Profile;
+import org.eclipse.osgi.service.datalocation.Location;
+import org.eclipse.osgi.service.resolver.*;
+import org.eclipse.osgi.service.runnable.ApplicationLauncher;
+import org.eclipse.osgi.service.runnable.StartupMonitor;
+import org.eclipse.osgi.util.ManifestElement;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.*;
+import org.osgi.service.packageadmin.PackageAdmin;
+import org.osgi.service.startlevel.StartLevel;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * Special startup class for the Eclipse Platform. This class cannot be
+ * instantiated; all functionality is provided by static methods.
+ * <p>
+ * The Eclipse Platform makes heavy use of Java class loaders for loading
+ * plug-ins. Even the Eclipse Runtime itself and the OSGi framework need
+ * to be loaded by special class loaders. The upshot is that a
+ * client program (such as a Java main program, a servlet) cannot
+ * reference any part of Eclipse directly. Instead, a client must use this
+ * loader class to start the platform, invoking functionality defined
+ * in plug-ins, and shutting down the platform when done.
+ * </p>
+ * <p>Note that the fields on this class are not API. </p>
+ * @since 3.0
+ * @noextend This class is not intended to be subclassed by clients.
+ */
+public class EclipseStarter {
+ private static FrameworkAdaptor adaptor;
+ private static BundleContext context;
+ private static boolean initialize = false;
+ public static boolean debug = false;
+ private static boolean running = false;
+ private static Framework framework = null;
+ private static ServiceRegistration<?> defaultMonitorRegistration = null;
+ private static ServiceRegistration<?> appLauncherRegistration = null;
+ private static ServiceRegistration<?> splashStreamRegistration = null;
+
+ // command line arguments
+ private static final String CLEAN = "-clean"; //$NON-NLS-1$
+ private static final String CONSOLE = "-console"; //$NON-NLS-1$
+ private static final String CONSOLE_LOG = "-consoleLog"; //$NON-NLS-1$
+ private static final String DEBUG = "-debug"; //$NON-NLS-1$
+ private static final String INITIALIZE = "-initialize"; //$NON-NLS-1$
+ private static final String DEV = "-dev"; //$NON-NLS-1$
+ private static final String WS = "-ws"; //$NON-NLS-1$
+ private static final String OS = "-os"; //$NON-NLS-1$
+ private static final String ARCH = "-arch"; //$NON-NLS-1$
+ private static final String NL = "-nl"; //$NON-NLS-1$
+ private static final String NL_EXTENSIONS = "-nlExtensions"; //$NON-NLS-1$
+ private static final String CONFIGURATION = "-configuration"; //$NON-NLS-1$
+ private static final String USER = "-user"; //$NON-NLS-1$
+ private static final String NOEXIT = "-noExit"; //$NON-NLS-1$
+ private static final String LAUNCHER = "-launcher"; //$NON-NLS-1$
+
+ // this is more of an Eclipse argument but this OSGi implementation stores its
+ // metadata alongside Eclipse's.
+ private static final String DATA = "-data"; //$NON-NLS-1$
+
+ // System properties
+ public static final String PROP_BUNDLES = "osgi.bundles"; //$NON-NLS-1$
+ public static final String PROP_BUNDLES_STARTLEVEL = "osgi.bundles.defaultStartLevel"; //$NON-NLS-1$ //The start level used to install the bundles
+ public static final String PROP_EXTENSIONS = "osgi.framework.extensions"; //$NON-NLS-1$
+ public static final String PROP_INITIAL_STARTLEVEL = "osgi.startLevel"; //$NON-NLS-1$ //The start level when the fwl start
+ public static final String PROP_DEBUG = "osgi.debug"; //$NON-NLS-1$
+ public static final String PROP_DEV = "osgi.dev"; //$NON-NLS-1$
+ public static final String PROP_CLEAN = "osgi.clean"; //$NON-NLS-1$
+ public static final String PROP_CONSOLE = "osgi.console"; //$NON-NLS-1$
+ public static final String PROP_CONSOLE_CLASS = "osgi.consoleClass"; //$NON-NLS-1$
+ public static final String PROP_CHECK_CONFIG = "osgi.checkConfiguration"; //$NON-NLS-1$
+ public static final String PROP_OS = "osgi.os"; //$NON-NLS-1$
+ public static final String PROP_WS = "osgi.ws"; //$NON-NLS-1$
+ public static final String PROP_NL = "osgi.nl"; //$NON-NLS-1$
+ private static final String PROP_NL_EXTENSIONS = "osgi.nl.extensions"; //$NON-NLS-1$
+ public static final String PROP_ARCH = "osgi.arch"; //$NON-NLS-1$
+ public static final String PROP_ADAPTOR = "osgi.adaptor"; //$NON-NLS-1$
+ public static final String PROP_SYSPATH = "osgi.syspath"; //$NON-NLS-1$
+ public static final String PROP_LOGFILE = "osgi.logfile"; //$NON-NLS-1$
+ public static final String PROP_FRAMEWORK = "osgi.framework"; //$NON-NLS-1$
+ public static final String PROP_INSTALL_AREA = "osgi.install.area"; //$NON-NLS-1$
+ public static final String PROP_FRAMEWORK_SHAPE = "osgi.framework.shape"; //$NON-NLS-1$ //the shape of the fwk (jar, or folder)
+ public static final String PROP_NOSHUTDOWN = "osgi.noShutdown"; //$NON-NLS-1$
+ private static final String PROP_FORCED_RESTART = "osgi.forcedRestart"; //$NON-NLS-1$
+
+ public static final String PROP_EXITCODE = "eclipse.exitcode"; //$NON-NLS-1$
+ public static final String PROP_EXITDATA = "eclipse.exitdata"; //$NON-NLS-1$
+ public static final String PROP_CONSOLE_LOG = "eclipse.consoleLog"; //$NON-NLS-1$
+ public static final String PROP_IGNOREAPP = "eclipse.ignoreApp"; //$NON-NLS-1$
+ public static final String PROP_REFRESH_BUNDLES = "eclipse.refreshBundles"; //$NON-NLS-1$
+ private static final String PROP_ALLOW_APPRELAUNCH = "eclipse.allowAppRelaunch"; //$NON-NLS-1$
+ private static final String PROP_APPLICATION_LAUNCHDEFAULT = "eclipse.application.launchDefault"; //$NON-NLS-1$
+
+ private static final String FILE_SCHEME = "file:"; //$NON-NLS-1$
+ private static final String REFERENCE_SCHEME = "reference:"; //$NON-NLS-1$
+ private static final String REFERENCE_PROTOCOL = "reference"; //$NON-NLS-1$
+ private static final String INITIAL_LOCATION = "initial@"; //$NON-NLS-1$
+ /** string containing the classname of the adaptor to be used in this framework instance */
+ protected static final String DEFAULT_ADAPTOR_CLASS = "org.eclipse.osgi.baseadaptor.BaseAdaptor"; //$NON-NLS-1$
+
+ private static final int DEFAULT_INITIAL_STARTLEVEL = 6; // default value for legacy purposes
+ private static final String DEFAULT_BUNDLES_STARTLEVEL = "4"; //$NON-NLS-1$
+
+ private static FrameworkLog log;
+ // directory of serch candidates keyed by directory abs path -> directory listing (bug 122024)
+ private static Map<String, String[]> searchCandidates = new HashMap<String, String[]>(4);
+ private static EclipseAppLauncher appLauncher;
+ private static List<Runnable> shutdownHandlers;
+
+ private static ConsoleManager consoleMgr = null;
+
+ /**
+ * This is the main to start osgi.
+ * It only works when the framework is being jared as a single jar
+ */
+ public static void main(String[] args) throws Exception {
+ if (FrameworkProperties.getProperty("eclipse.startTime") == null) //$NON-NLS-1$
+ FrameworkProperties.setProperty("eclipse.startTime", Long.toString(System.currentTimeMillis())); //$NON-NLS-1$
+ if (FrameworkProperties.getProperty(PROP_NOSHUTDOWN) == null)
+ FrameworkProperties.setProperty(PROP_NOSHUTDOWN, "true"); //$NON-NLS-1$
+ // set the compatibility boot delegation flag to false to get "standard" OSGi behavior WRT boot delegation (bug 178477)
+ if (FrameworkProperties.getProperty(Constants.OSGI_COMPATIBILITY_BOOTDELEGATION) == null)
+ FrameworkProperties.setProperty(Constants.OSGI_COMPATIBILITY_BOOTDELEGATION, "false"); //$NON-NLS-1$
+ Object result = run(args, null);
+ if (result instanceof Integer && !Boolean.valueOf(FrameworkProperties.getProperty(PROP_NOSHUTDOWN)).booleanValue())
+ System.exit(((Integer) result).intValue());
+ }
+
+ /**
+ * Launches the platform and runs a single application. The application is either identified
+ * in the given arguments (e.g., -application &lt;app id&gt;) or in the <code>eclipse.application</code>
+ * System property. This convenience method starts
+ * up the platform, runs the indicated application, and then shuts down the
+ * platform. The platform must not be running already.
+ *
+ * @param args the command line-style arguments used to configure the platform
+ * @param endSplashHandler the block of code to run to tear down the splash
+ * screen or <code>null</code> if no tear down is required
+ * @return the result of running the application
+ * @throws Exception if anything goes wrong
+ */
+ public static Object run(String[] args, Runnable endSplashHandler) throws Exception {
+ if (Profile.PROFILE && Profile.STARTUP)
+ Profile.logEnter("EclipseStarter.run()", null); //$NON-NLS-1$
+ if (running)
+ throw new IllegalStateException(EclipseAdaptorMsg.ECLIPSE_STARTUP_ALREADY_RUNNING);
+ boolean startupFailed = true;
+ try {
+ startup(args, endSplashHandler);
+ startupFailed = false;
+ if (Boolean.valueOf(FrameworkProperties.getProperty(PROP_IGNOREAPP)).booleanValue() || isForcedRestart())
+ return null;
+ return run(null);
+ } catch (Throwable e) {
+ // ensure the splash screen is down
+ if (endSplashHandler != null)
+ endSplashHandler.run();
+ // may use startupFailed to understand where the error happened
+ FrameworkLogEntry logEntry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, startupFailed ? EclipseAdaptorMsg.ECLIPSE_STARTUP_STARTUP_ERROR : EclipseAdaptorMsg.ECLIPSE_STARTUP_APP_ERROR, 1, e, null);
+ if (log != null)
+ log.log(logEntry);
+ else
+ // TODO desperate measure - ideally, we should write this to disk (a la Main.log)
+ e.printStackTrace();
+ } finally {
+ try {
+ // The application typically sets the exit code however the framework can request that
+ // it be re-started. We need to check for this and potentially override the exit code.
+ if (isForcedRestart())
+ FrameworkProperties.setProperty(PROP_EXITCODE, "23"); //$NON-NLS-1$
+ if (!Boolean.valueOf(FrameworkProperties.getProperty(PROP_NOSHUTDOWN)).booleanValue())
+ shutdown();
+ } catch (Throwable e) {
+ FrameworkLogEntry logEntry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, EclipseAdaptorMsg.ECLIPSE_STARTUP_SHUTDOWN_ERROR, 1, e, null);
+ if (log != null)
+ log.log(logEntry);
+ else
+ // TODO desperate measure - ideally, we should write this to disk (a la Main.log)
+ e.printStackTrace();
+ }
+ if (Profile.PROFILE && Profile.STARTUP)
+ Profile.logExit("EclipseStarter.run()"); //$NON-NLS-1$
+ if (Profile.PROFILE) {
+ String report = Profile.getProfileLog();
+ // avoiding writing to the console if there is nothing to print
+ if (report != null && report.length() > 0)
+ System.out.println(report);
+ }
+ }
+ // we only get here if an error happened
+ if (FrameworkProperties.getProperty(PROP_EXITCODE) == null) {
+ FrameworkProperties.setProperty(PROP_EXITCODE, "13"); //$NON-NLS-1$
+ FrameworkProperties.setProperty(PROP_EXITDATA, NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_ERROR_CHECK_LOG, log == null ? null : log.getFile().getPath()));
+ }
+ return null;
+ }
+
+ /**
+ * Returns true if the platform is already running, false otherwise.
+ * @return whether or not the platform is already running
+ */
+ public static boolean isRunning() {
+ return running;
+ }
+
+ /**
+ * Starts the platform and sets it up to run a single application. The application is either identified
+ * in the given arguments (e.g., -application &lt;app id&gt;) or in the <code>eclipse.application</code>
+ * System property. The platform must not be running already.
+ * <p>
+ * The given runnable (if not <code>null</code>) is used to tear down the splash screen if required.
+ * </p>
+ * @param args the arguments passed to the application
+ * @return BundleContext the context of the system bundle
+ * @throws Exception if anything goes wrong
+ */
+ public static BundleContext startup(String[] args, Runnable endSplashHandler) throws Exception {
+ if (Profile.PROFILE && Profile.STARTUP)
+ Profile.logEnter("EclipseStarter.startup()", null); //$NON-NLS-1$
+ if (running)
+ throw new IllegalStateException(EclipseAdaptorMsg.ECLIPSE_STARTUP_ALREADY_RUNNING);
+ FrameworkProperties.initializeProperties();
+ processCommandLine(args);
+ LocationManager.initializeLocations();
+ loadConfigurationInfo();
+ finalizeProperties();
+ if (Profile.PROFILE)
+ Profile.initProps(); // catch any Profile properties set in eclipse.properties...
+ if (Profile.PROFILE && Profile.STARTUP)
+ Profile.logTime("EclipseStarter.startup()", "props inited"); //$NON-NLS-1$ //$NON-NLS-2$
+ adaptor = createAdaptor();
+ log = adaptor.getFrameworkLog();
+ if (Profile.PROFILE && Profile.STARTUP)
+ Profile.logTime("EclipseStarter.startup()", "adapter created"); //$NON-NLS-1$ //$NON-NLS-2$
+ framework = new Framework(adaptor);
+ if (Profile.PROFILE && Profile.STARTUP)
+ Profile.logTime("EclipseStarter.startup()", "OSGi created"); //$NON-NLS-1$ //$NON-NLS-2$
+ context = framework.getBundle(0).getBundleContext();
+ registerFrameworkShutdownHandlers();
+ publishSplashScreen(endSplashHandler);
+ if (Profile.PROFILE && Profile.STARTUP)
+ Profile.logTime("EclipseStarter.startup()", "osgi launched"); //$NON-NLS-1$ //$NON-NLS-2$
+ consoleMgr = ConsoleManager.startConsole(framework);
+ if (Profile.PROFILE && Profile.STARTUP) {
+ Profile.logTime("EclipseStarter.startup()", "console started"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ framework.launch();
+ // save the cached timestamp before loading basic bundles; this is needed so we can do a proper timestamp check when logging resolver errors
+ long stateStamp = adaptor.getState().getTimeStamp();
+ Bundle[] startBundles = loadBasicBundles();
+
+ if (startBundles == null || ("true".equals(FrameworkProperties.getProperty(PROP_REFRESH_BUNDLES)) && refreshPackages(getCurrentBundles(false)))) { //$NON-NLS-1$
+ waitForShutdown();
+ return context; // cannot continue; loadBasicBundles caused refreshPackages to shutdown the framework
+ }
+
+ if (Profile.PROFILE && Profile.STARTUP)
+ Profile.logTime("EclipseStarter.startup()", "loading basic bundles"); //$NON-NLS-1$ //$NON-NLS-2$
+
+ // set the framework start level to the ultimate value. This will actually start things
+ // running if they are persistently active.
+ setStartLevel(getStartLevel());
+ if (Profile.PROFILE && Profile.STARTUP)
+ Profile.logTime("EclipseStarter.startup()", "StartLevel set"); //$NON-NLS-1$ //$NON-NLS-2$
+ // they should all be active by this time
+ ensureBundlesActive(startBundles);
+
+ // in the case where the built-in console is disabled we should try to start the console bundle
+ try {
+ consoleMgr.checkForConsoleBundle();
+ } catch (BundleException e) {
+ FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, e.getMessage(), 0, e, null);
+ log.log(entry);
+ }
+ if (debug || FrameworkProperties.getProperty(PROP_DEV) != null)
+ // only spend time showing unresolved bundles in dev/debug mode and the state has changed
+ if (stateStamp != adaptor.getState().getTimeStamp())
+ logUnresolvedBundles(context.getBundles());
+ running = true;
+ if (Profile.PROFILE && Profile.STARTUP)
+ Profile.logExit("EclipseStarter.startup()"); //$NON-NLS-1$
+ return context;
+ }
+
+ private static int getStartLevel() {
+ String level = FrameworkProperties.getProperty(PROP_INITIAL_STARTLEVEL);
+ if (level != null)
+ try {
+ return Integer.parseInt(level);
+ } catch (NumberFormatException e) {
+ if (debug)
+ System.out.println("Start level = " + level + " parsed. Using hardcoded default: 6"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ return DEFAULT_INITIAL_STARTLEVEL;
+ }
+
+ /**
+ * Runs the application for which the platform was started. The platform
+ * must be running.
+ * <p>
+ * The given argument is passed to the application being run. If it is <code>null</code>
+ * then the command line arguments used in starting the platform, and not consumed
+ * by the platform code, are passed to the application as a <code>String[]</code>.
+ * </p>
+ * @param argument the argument passed to the application. May be <code>null</code>
+ * @return the result of running the application
+ * @throws Exception if anything goes wrong
+ */
+ public static Object run(Object argument) throws Exception {
+ if (Profile.PROFILE && Profile.STARTUP)
+ Profile.logEnter("EclipseStarter.run(Object)()", null); //$NON-NLS-1$
+ if (!running)
+ throw new IllegalStateException(EclipseAdaptorMsg.ECLIPSE_STARTUP_NOT_RUNNING);
+ // if we are just initializing, do not run the application just return.
+ if (initialize)
+ return new Integer(0);
+ try {
+ if (appLauncher == null) {
+ boolean launchDefault = Boolean.valueOf(FrameworkProperties.getProperty(PROP_APPLICATION_LAUNCHDEFAULT, "true")).booleanValue(); //$NON-NLS-1$
+ // create the ApplicationLauncher and register it as a service
+ appLauncher = new EclipseAppLauncher(context, Boolean.valueOf(FrameworkProperties.getProperty(PROP_ALLOW_APPRELAUNCH)).booleanValue(), launchDefault, log);
+ appLauncherRegistration = context.registerService(ApplicationLauncher.class.getName(), appLauncher, null);
+ // must start the launcher AFTER service restration because this method
+ // blocks and runs the application on the current thread. This method
+ // will return only after the application has stopped.
+ return appLauncher.start(argument);
+ }
+ return appLauncher.reStart(argument);
+ } catch (Exception e) {
+ if (log != null && context != null) // context can be null if OSGi failed to launch (bug 151413)
+ logUnresolvedBundles(context.getBundles());
+ throw e;
+ }
+ }
+
+ /**
+ * Shuts down the Platform. The state of the Platform is not automatically
+ * saved before shutting down.
+ * <p>
+ * On return, the Platform will no longer be running (but could be re-launched
+ * with another call to startup). If relaunching, care must be taken to reinitialize
+ * any System properties which the platform uses (e.g., osgi.instance.area) as
+ * some policies in the platform do not allow resetting of such properties on
+ * subsequent runs.
+ * </p><p>
+ * Any objects handed out by running Platform,
+ * including Platform runnables obtained via getRunnable, will be
+ * permanently invalid. The effects of attempting to invoke methods
+ * on invalid objects is undefined.
+ * </p>
+ * @throws Exception if anything goes wrong
+ */
+ public static void shutdown() throws Exception {
+ if (!running || framework == null)
+ return;
+ if (appLauncherRegistration != null)
+ appLauncherRegistration.unregister();
+ if (splashStreamRegistration != null)
+ splashStreamRegistration.unregister();
+ if (defaultMonitorRegistration != null)
+ defaultMonitorRegistration.unregister();
+ if (appLauncher != null)
+ appLauncher.shutdown();
+ appLauncherRegistration = null;
+ appLauncher = null;
+ splashStreamRegistration = null;
+ defaultMonitorRegistration = null;
+ if (consoleMgr != null) {
+ consoleMgr.stopConsole();
+ consoleMgr = null;
+ }
+ framework.close();
+ framework = null;
+ context = null;
+ running = false;
+ }
+
+ private static void ensureBundlesActive(Bundle[] bundles) {
+ ServiceTracker<StartLevel, StartLevel> tracker = null;
+ try {
+ for (int i = 0; i < bundles.length; i++) {
+ if (bundles[i].getState() != Bundle.ACTIVE) {
+ if (bundles[i].getState() == Bundle.INSTALLED) {
+ // Log that the bundle is not resolved
+ log.log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_ERROR_BUNDLE_NOT_RESOLVED, bundles[i].getLocation()), 0, null, null));
+ continue;
+ }
+ // check that the startlevel allows the bundle to be active (111550)
+ if (tracker == null) {
+ tracker = new ServiceTracker<StartLevel, StartLevel>(context, StartLevel.class.getName(), null);
+ tracker.open();
+ }
+ StartLevel sl = tracker.getService();
+ if (sl != null && (sl.getBundleStartLevel(bundles[i]) <= sl.getStartLevel())) {
+ log.log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_ERROR_BUNDLE_NOT_ACTIVE, bundles[i]), 0, null, null));
+ }
+ }
+ }
+ } finally {
+ if (tracker != null)
+ tracker.close();
+ }
+ }
+
+ private static void logUnresolvedBundles(Bundle[] bundles) {
+ State state = adaptor.getState();
+ FrameworkLog logService = adaptor.getFrameworkLog();
+ StateHelper stateHelper = adaptor.getPlatformAdmin().getStateHelper();
+
+ // first lets look for missing leaf constraints (bug 114120)
+ VersionConstraint[] leafConstraints = stateHelper.getUnsatisfiedLeaves(state.getBundles());
+ // hash the missing leaf constraints by the declaring bundles
+ Map<BundleDescription, List<VersionConstraint>> missing = new HashMap<BundleDescription, List<VersionConstraint>>();
+ for (int i = 0; i < leafConstraints.length; i++) {
+ // only include non-optional and non-dynamic constraint leafs
+ if (leafConstraints[i] instanceof BundleSpecification && ((BundleSpecification) leafConstraints[i]).isOptional())
+ continue;
+ if (leafConstraints[i] instanceof ImportPackageSpecification) {
+ if (ImportPackageSpecification.RESOLUTION_OPTIONAL.equals(((ImportPackageSpecification) leafConstraints[i]).getDirective(Constants.RESOLUTION_DIRECTIVE)))
+ continue;
+ if (ImportPackageSpecification.RESOLUTION_DYNAMIC.equals(((ImportPackageSpecification) leafConstraints[i]).getDirective(Constants.RESOLUTION_DIRECTIVE)))
+ continue;
+ }
+ BundleDescription bundle = leafConstraints[i].getBundle();
+ List<VersionConstraint> constraints = missing.get(bundle);
+ if (constraints == null) {
+ constraints = new ArrayList<VersionConstraint>();
+ missing.put(bundle, constraints);
+ }
+ constraints.add(leafConstraints[i]);
+ }
+
+ // found some bundles with missing leaf constraints; log them first
+ if (missing.size() > 0) {
+ FrameworkLogEntry[] rootChildren = new FrameworkLogEntry[missing.size()];
+ int rootIndex = 0;
+ for (Iterator<BundleDescription> iter = missing.keySet().iterator(); iter.hasNext(); rootIndex++) {
+ BundleDescription description = iter.next();
+ String symbolicName = description.getSymbolicName() == null ? FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME : description.getSymbolicName();
+ String generalMessage = NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_ERROR_BUNDLE_NOT_RESOLVED, description.getLocation());
+ List<VersionConstraint> constraints = missing.get(description);
+ FrameworkLogEntry[] logChildren = new FrameworkLogEntry[constraints.size()];
+ for (int i = 0; i < logChildren.length; i++)
+ logChildren[i] = new FrameworkLogEntry(symbolicName, FrameworkLogEntry.WARNING, 0, MessageHelper.getResolutionFailureMessage(constraints.get(i)), 0, null, null);
+ rootChildren[rootIndex] = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.WARNING, 0, generalMessage, 0, null, logChildren);
+ }
+ logService.log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.WARNING, 0, EclipseAdaptorMsg.ECLIPSE_STARTUP_ROOTS_NOT_RESOLVED, 0, null, rootChildren));
+ }
+
+ // There may be some bundles unresolved for other reasons, causing the system to be unresolved
+ // log all unresolved constraints now
+ List<FrameworkLogEntry> allChildren = new ArrayList<FrameworkLogEntry>();
+ for (int i = 0; i < bundles.length; i++)
+ if (bundles[i].getState() == Bundle.INSTALLED) {
+ String symbolicName = bundles[i].getSymbolicName() == null ? FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME : bundles[i].getSymbolicName();
+ String generalMessage = NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_ERROR_BUNDLE_NOT_RESOLVED, bundles[i]);
+ BundleDescription description = state.getBundle(bundles[i].getBundleId());
+ // for some reason, the state does not know about that bundle
+ if (description == null)
+ continue;
+ FrameworkLogEntry[] logChildren = null;
+ VersionConstraint[] unsatisfied = stateHelper.getUnsatisfiedConstraints(description);
+ if (unsatisfied.length > 0) {
+ // the bundle wasn't resolved due to some of its constraints were unsatisfiable
+ logChildren = new FrameworkLogEntry[unsatisfied.length];
+ for (int j = 0; j < unsatisfied.length; j++)
+ logChildren[j] = new FrameworkLogEntry(symbolicName, FrameworkLogEntry.WARNING, 0, MessageHelper.getResolutionFailureMessage(unsatisfied[j]), 0, null, null);
+ } else {
+ ResolverError[] resolverErrors = state.getResolverErrors(description);
+ if (resolverErrors.length > 0) {
+ logChildren = new FrameworkLogEntry[resolverErrors.length];
+ for (int j = 0; j < resolverErrors.length; j++)
+ logChildren[j] = new FrameworkLogEntry(symbolicName, FrameworkLogEntry.WARNING, 0, resolverErrors[j].toString(), 0, null, null);
+ }
+ }
+
+ allChildren.add(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.WARNING, 0, generalMessage, 0, null, logChildren));
+ }
+ if (allChildren.size() > 0)
+ logService.log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.WARNING, 0, EclipseAdaptorMsg.ECLIPSE_STARTUP_ALL_NOT_RESOLVED, 0, null, allChildren.toArray(new FrameworkLogEntry[allChildren.size()])));
+ }
+
+ private static void publishSplashScreen(final Runnable endSplashHandler) {
+ if (endSplashHandler == null)
+ return;
+ // register the output stream to the launcher if it exists
+ try {
+ Method method = endSplashHandler.getClass().getMethod("getOutputStream", new Class[0]); //$NON-NLS-1$
+ Object outputStream = method.invoke(endSplashHandler, new Object[0]);
+ if (outputStream instanceof OutputStream) {
+ Dictionary<String, Object> osProperties = new Hashtable<String, Object>();
+ osProperties.put("name", "splashstream"); //$NON-NLS-1$//$NON-NLS-2$
+ splashStreamRegistration = context.registerService(OutputStream.class.getName(), outputStream, osProperties);
+ }
+ } catch (Exception ex) {
+ // ignore
+ }
+ // keep this splash handler as the default startup monitor
+ try {
+ Dictionary<String, Object> monitorProps = new Hashtable<String, Object>();
+ monitorProps.put(Constants.SERVICE_RANKING, new Integer(Integer.MIN_VALUE));
+ defaultMonitorRegistration = context.registerService(StartupMonitor.class.getName(), new DefaultStartupMonitor(endSplashHandler), monitorProps);
+ } catch (IllegalStateException e) {
+ //splash handler did not provide the necessary methods, ignore it
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ private static URL searchForBundle(String name, String parent) throws MalformedURLException {
+ URL url = null;
+ File fileLocation = null;
+ boolean reference = false;
+ try {
+ new URL(name); // quick check to see if the name is a valid URL
+ url = new URL(new File(parent).toURL(), name);
+ } catch (MalformedURLException e) {
+ // TODO this is legacy support for non-URL names. It should be removed eventually.
+ // if name was not a URL then construct one.
+ // Assume it should be a reference and that it is relative. This support need not
+ // be robust as it is temporary..
+ File child = new File(name);
+ fileLocation = child.isAbsolute() ? child : new File(parent, name);
+ url = new URL(REFERENCE_PROTOCOL, null, fileLocation.toURL().toExternalForm());
+ reference = true;
+ }
+ // if the name was a URL then see if it is relative. If so, insert syspath.
+ if (!reference) {
+ URL baseURL = url;
+ // if it is a reference URL then strip off the reference: and set base to the file:...
+ if (url.getProtocol().equals(REFERENCE_PROTOCOL)) {
+ reference = true;
+ String baseSpec = url.getFile();
+ if (baseSpec.startsWith(FILE_SCHEME)) {
+ File child = new File(baseSpec.substring(5));
+ baseURL = child.isAbsolute() ? child.toURL() : new File(parent, child.getPath()).toURL();
+ } else
+ baseURL = new URL(baseSpec);
+ }
+
+ fileLocation = new File(baseURL.getFile());
+ // if the location is relative, prefix it with the parent
+ if (!fileLocation.isAbsolute())
+ fileLocation = new File(parent, fileLocation.toString());
+ }
+ // If the result is a reference then search for the real result and
+ // reconstruct the answer.
+ if (reference) {
+ String result = searchFor(fileLocation.getName(), new File(fileLocation.getParent()).getAbsolutePath());
+ if (result != null)
+ url = new URL(REFERENCE_PROTOCOL, null, FILE_SCHEME + result);
+ else
+ return null;
+ }
+
+ // finally we have something worth trying
+ try {
+ URLConnection result = url.openConnection();
+ result.connect();
+ return url;
+ } catch (IOException e) {
+ // int i = location.lastIndexOf('_');
+ // return i == -1? location : location.substring(0, i);
+ return null;
+ }
+ }
+
+ /*
+ * Ensure all basic bundles are installed, resolved and scheduled to start. Returns an array containing
+ * all basic bundles that are marked to start.
+ * Returns null if the framework has been shutdown as a result of refreshPackages
+ */
+ private static Bundle[] loadBasicBundles() {
+ long startTime = System.currentTimeMillis();
+ String osgiBundles = FrameworkProperties.getProperty(PROP_BUNDLES);
+ String osgiExtensions = FrameworkProperties.getProperty(PROP_EXTENSIONS);
+ if (osgiExtensions != null && osgiExtensions.length() > 0) {
+ osgiBundles = osgiExtensions + ',' + osgiBundles;
+ FrameworkProperties.setProperty(PROP_BUNDLES, osgiBundles);
+ }
+ String[] installEntries = getArrayFromList(osgiBundles, ","); //$NON-NLS-1$
+ // get the initial bundle list from the installEntries
+ InitialBundle[] initialBundles = getInitialBundles(installEntries);
+ // get the list of currently installed initial bundles from the framework
+ Bundle[] curInitBundles = getCurrentBundles(true);
+
+ // list of bundles to be refreshed
+ List<Bundle> toRefresh = new ArrayList<Bundle>(curInitBundles.length);
+ // uninstall any of the currently installed bundles that do not exist in the
+ // initial bundle list from installEntries.
+ uninstallBundles(curInitBundles, initialBundles, toRefresh);
+
+ // install the initialBundles that are not already installed.
+ List<Bundle> startBundles = new ArrayList<Bundle>(installEntries.length);
+ List<Bundle> lazyActivationBundles = new ArrayList<Bundle>(installEntries.length);
+ installBundles(initialBundles, curInitBundles, startBundles, lazyActivationBundles, toRefresh);
+
+ // If we installed/uninstalled something, force a refresh of all installed/uninstalled bundles
+ if (!toRefresh.isEmpty() && refreshPackages(toRefresh.toArray(new Bundle[toRefresh.size()])))
+ return null; // cannot continue; refreshPackages shutdown the framework
+
+ // schedule all basic bundles to be started
+ Bundle[] startInitBundles = startBundles.toArray(new Bundle[startBundles.size()]);
+ Bundle[] lazyInitBundles = lazyActivationBundles.toArray(new Bundle[lazyActivationBundles.size()]);
+ startBundles(startInitBundles, lazyInitBundles);
+
+ if (debug)
+ System.out.println("Time to load bundles: " + (System.currentTimeMillis() - startTime)); //$NON-NLS-1$
+ return startInitBundles;
+ }
+
+ private static InitialBundle[] getInitialBundles(String[] installEntries) {
+ searchCandidates.clear();
+ List<InitialBundle> result = new ArrayList<InitialBundle>(installEntries.length);
+ int defaultStartLevel = Integer.parseInt(FrameworkProperties.getProperty(PROP_BUNDLES_STARTLEVEL, DEFAULT_BUNDLES_STARTLEVEL));
+ String syspath = getSysPath();
+ // should canonicalize the syspath.
+ try {
+ syspath = new File(syspath).getCanonicalPath();
+ } catch (IOException ioe) {
+ // do nothing
+ }
+ for (int i = 0; i < installEntries.length; i++) {
+ String name = installEntries[i];
+ int level = defaultStartLevel;
+ boolean start = false;
+ int index = name.lastIndexOf('@');
+ if (index >= 0) {
+ String[] attributes = getArrayFromList(name.substring(index + 1, name.length()), ":"); //$NON-NLS-1$
+ for (int j = 0; j < attributes.length; j++) {
+ String attribute = attributes[j];
+ if (attribute.equals("start")) //$NON-NLS-1$
+ start = true;
+ else {
+ try {
+ level = Integer.parseInt(attribute);
+ } catch (NumberFormatException e) { // bug 188089
+ index = name.length();
+ continue;
+ }
+ }
+ }
+ name = name.substring(0, index);
+ }
+ try {
+ URL location = searchForBundle(name, syspath);
+ if (location == null) {
+ FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_BUNDLE_NOT_FOUND, installEntries[i]), 0, null, null);
+ log.log(entry);
+ // skip this entry
+ continue;
+ }
+ location = makeRelative(LocationManager.getInstallLocation().getURL(), location);
+ String locationString = INITIAL_LOCATION + location.toExternalForm();
+ result.add(new InitialBundle(locationString, location, level, start));
+ } catch (IOException e) {
+ log.log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, e.getMessage(), 0, e, null));
+ }
+ }
+ return result.toArray(new InitialBundle[result.size()]);
+ }
+
+ // returns true if the refreshPackages operation caused the framework to shutdown
+ private static boolean refreshPackages(Bundle[] bundles) {
+ ServiceReference<?> packageAdminRef = context.getServiceReference(PackageAdmin.class.getName());
+ PackageAdmin packageAdmin = null;
+ if (packageAdminRef != null)
+ packageAdmin = (PackageAdmin) context.getService(packageAdminRef);
+ if (packageAdmin == null)
+ return false;
+ // TODO this is such a hack it is silly. There are still cases for race conditions etc
+ // but this should allow for some progress...
+ final Semaphore semaphore = new Semaphore(0);
+ StartupEventListener listener = new StartupEventListener(semaphore, FrameworkEvent.PACKAGES_REFRESHED);
+ context.addFrameworkListener(listener);
+ context.addBundleListener(listener);
+ packageAdmin.refreshPackages(bundles);
+ context.ungetService(packageAdminRef);
+ updateSplash(semaphore, listener);
+ if (isForcedRestart())
+ return true;
+ return false;
+ }
+
+ private static void waitForShutdown() {
+ if (!isForcedRestart())
+ return;
+ // wait for the system bundle to stop
+ Bundle systemBundle = framework.getBundle(0);
+ int i = 0;
+ while (i < 5000 && (systemBundle.getState() & (Bundle.STARTING | Bundle.ACTIVE | Bundle.STOPPING)) != 0) {
+ i += 200;
+ try {
+ Thread.sleep(200);
+ } catch (InterruptedException e) {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Creates and returns the adaptor
+ *
+ * @return a FrameworkAdaptor object
+ */
+ private static FrameworkAdaptor createAdaptor() throws Exception {
+ String adaptorClassName = FrameworkProperties.getProperty(PROP_ADAPTOR, DEFAULT_ADAPTOR_CLASS);
+ Class<?> adaptorClass = Class.forName(adaptorClassName);
+ Class<?>[] constructorArgs = new Class[] {String[].class};
+ Constructor<?> constructor = adaptorClass.getConstructor(constructorArgs);
+ return (FrameworkAdaptor) constructor.newInstance(new Object[] {new String[0]});
+ }
+
+ private static String[] processCommandLine(String[] args) throws Exception {
+ EclipseEnvironmentInfo.setAllArgs(args);
+ if (args.length == 0) {
+ EclipseEnvironmentInfo.setFrameworkArgs(args);
+ EclipseEnvironmentInfo.setAllArgs(args);
+ return args;
+ }
+ int[] configArgs = new int[args.length];
+ configArgs[0] = -1; // need to initialize the first element to something that could not be an index.
+ int configArgIndex = 0;
+ for (int i = 0; i < args.length; i++) {
+ boolean found = false;
+ // check for args without parameters (i.e., a flag arg)
+
+ // check if debug should be enabled for the entire platform
+ // If this is the last arg or there is a following arg (i.e., arg+1 has a leading -),
+ // simply enable debug. Otherwise, assume that that the following arg is
+ // actually the filename of an options file. This will be processed below.
+ if (args[i].equalsIgnoreCase(DEBUG) && ((i + 1 == args.length) || ((i + 1 < args.length) && (args[i + 1].startsWith("-"))))) { //$NON-NLS-1$
+ FrameworkProperties.setProperty(PROP_DEBUG, ""); //$NON-NLS-1$
+ debug = true;
+ found = true;
+ }
+
+ // check if development mode should be enabled for the entire platform
+ // If this is the last arg or there is a following arg (i.e., arg+1 has a leading -),
+ // simply enable development mode. Otherwise, assume that that the following arg is
+ // actually some additional development time class path entries. This will be processed below.
+ if (args[i].equalsIgnoreCase(DEV) && ((i + 1 == args.length) || ((i + 1 < args.length) && (args[i + 1].startsWith("-"))))) { //$NON-NLS-1$
+ FrameworkProperties.setProperty(PROP_DEV, ""); //$NON-NLS-1$
+ found = true;
+ }
+
+ // look for the initialization arg
+ if (args[i].equalsIgnoreCase(INITIALIZE)) {
+ initialize = true;
+ found = true;
+ }
+
+ // look for the clean flag.
+ if (args[i].equalsIgnoreCase(CLEAN)) {
+ FrameworkProperties.setProperty(PROP_CLEAN, "true"); //$NON-NLS-1$
+ found = true;
+ }
+
+ // look for the consoleLog flag
+ if (args[i].equalsIgnoreCase(CONSOLE_LOG)) {
+ FrameworkProperties.setProperty(PROP_CONSOLE_LOG, "true"); //$NON-NLS-1$
+ found = true;
+ }
+
+ // look for the console with no port.
+ if (args[i].equalsIgnoreCase(CONSOLE) && ((i + 1 == args.length) || ((i + 1 < args.length) && (args[i + 1].startsWith("-"))))) { //$NON-NLS-1$
+ FrameworkProperties.setProperty(PROP_CONSOLE, ""); //$NON-NLS-1$
+ found = true;
+ }
+
+ if (args[i].equalsIgnoreCase(NOEXIT)) {
+ FrameworkProperties.setProperty(PROP_NOSHUTDOWN, "true"); //$NON-NLS-1$
+ found = true;
+ }
+
+ if (found) {
+ configArgs[configArgIndex++] = i;
+ continue;
+ }
+ // check for args with parameters. If we are at the last argument or if the next one
+ // has a '-' as the first character, then we can't have an arg with a parm so continue.
+ if (i == args.length - 1 || args[i + 1].startsWith("-")) { //$NON-NLS-1$
+ continue;
+ }
+ String arg = args[++i];
+
+ // look for the console and port.
+ if (args[i - 1].equalsIgnoreCase(CONSOLE)) {
+ FrameworkProperties.setProperty(PROP_CONSOLE, arg);
+ found = true;
+ }
+
+ // look for the configuration location .
+ if (args[i - 1].equalsIgnoreCase(CONFIGURATION)) {
+ FrameworkProperties.setProperty(LocationManager.PROP_CONFIG_AREA, arg);
+ found = true;
+ }
+
+ // look for the data location for this instance.
+ if (args[i - 1].equalsIgnoreCase(DATA)) {
+ FrameworkProperties.setProperty(LocationManager.PROP_INSTANCE_AREA, arg);
+ found = true;
+ }
+
+ // look for the user location for this instance.
+ if (args[i - 1].equalsIgnoreCase(USER)) {
+ FrameworkProperties.setProperty(LocationManager.PROP_USER_AREA, arg);
+ found = true;
+ }
+
+ // look for the launcher location
+ if (args[i - 1].equalsIgnoreCase(LAUNCHER)) {
+ FrameworkProperties.setProperty(LocationManager.PROP_LAUNCHER, arg);
+ found = true;
+ }
+ // look for the development mode and class path entries.
+ if (args[i - 1].equalsIgnoreCase(DEV)) {
+ FrameworkProperties.setProperty(PROP_DEV, arg);
+ found = true;
+ }
+
+ // look for the debug mode and option file location.
+ if (args[i - 1].equalsIgnoreCase(DEBUG)) {
+ FrameworkProperties.setProperty(PROP_DEBUG, arg);
+ debug = true;
+ found = true;
+ }
+
+ // look for the window system.
+ if (args[i - 1].equalsIgnoreCase(WS)) {
+ FrameworkProperties.setProperty(PROP_WS, arg);
+ found = true;
+ }
+
+ // look for the operating system
+ if (args[i - 1].equalsIgnoreCase(OS)) {
+ FrameworkProperties.setProperty(PROP_OS, arg);
+ found = true;
+ }
+
+ // look for the system architecture
+ if (args[i - 1].equalsIgnoreCase(ARCH)) {
+ FrameworkProperties.setProperty(PROP_ARCH, arg);
+ found = true;
+ }
+
+ // look for the nationality/language
+ if (args[i - 1].equalsIgnoreCase(NL)) {
+ FrameworkProperties.setProperty(PROP_NL, arg);
+ found = true;
+ }
+
+ // look for the locale extensions
+ if (args[i - 1].equalsIgnoreCase(NL_EXTENSIONS)) {
+ FrameworkProperties.setProperty(PROP_NL_EXTENSIONS, arg);
+ found = true;
+ }
+
+ // done checking for args. Remember where an arg was found
+ if (found) {
+ configArgs[configArgIndex++] = i - 1;
+ configArgs[configArgIndex++] = i;
+ }
+ }
+
+ // remove all the arguments consumed by this argument parsing
+ if (configArgIndex == 0) {
+ EclipseEnvironmentInfo.setFrameworkArgs(new String[0]);
+ EclipseEnvironmentInfo.setAppArgs(args);
+ return args;
+ }
+ String[] appArgs = new String[args.length - configArgIndex];
+ String[] frameworkArgs = new String[configArgIndex];
+ configArgIndex = 0;
+ int j = 0;
+ int k = 0;
+ for (int i = 0; i < args.length; i++) {
+ if (i == configArgs[configArgIndex]) {
+ frameworkArgs[k++] = args[i];
+ configArgIndex++;
+ } else
+ appArgs[j++] = args[i];
+ }
+ EclipseEnvironmentInfo.setFrameworkArgs(frameworkArgs);
+ EclipseEnvironmentInfo.setAppArgs(appArgs);
+ return appArgs;
+ }
+
+ /**
+ * Returns the result of converting a list of comma-separated tokens into an array
+ *
+ * @return the array of string tokens
+ * @param prop the initial comma-separated string
+ */
+ private static String[] getArrayFromList(String prop, String separator) {
+ return ManifestElement.getArrayFromList(prop, separator);
+ }
+
+ protected static String getSysPath() {
+ String result = FrameworkProperties.getProperty(PROP_SYSPATH);
+ if (result != null)
+ return result;
+ result = getSysPathFromURL(FrameworkProperties.getProperty(PROP_FRAMEWORK));
+ if (result == null)
+ result = getSysPathFromCodeSource();
+ if (result == null)
+ throw new IllegalStateException("Can not find the system path."); //$NON-NLS-1$
+ if (Character.isUpperCase(result.charAt(0))) {
+ char[] chars = result.toCharArray();
+ chars[0] = Character.toLowerCase(chars[0]);
+ result = new String(chars);
+ }
+ FrameworkProperties.setProperty(PROP_SYSPATH, result);
+ return result;
+ }
+
+ private static String getSysPathFromURL(String urlSpec) {
+ if (urlSpec == null)
+ return null;
+ URL url = LocationHelper.buildURL(urlSpec, false);
+ if (url == null)
+ return null;
+ File fwkFile = new File(url.getFile());
+ fwkFile = new File(fwkFile.getAbsolutePath());
+ fwkFile = new File(fwkFile.getParent());
+ return fwkFile.getAbsolutePath();
+ }
+
+ private static String getSysPathFromCodeSource() {
+ ProtectionDomain pd = EclipseStarter.class.getProtectionDomain();
+ if (pd == null)
+ return null;
+ CodeSource cs = pd.getCodeSource();
+ if (cs == null)
+ return null;
+ URL url = cs.getLocation();
+ if (url == null)
+ return null;
+ String result = url.getFile();
+ if (result.endsWith(".jar")) { //$NON-NLS-1$
+ result = result.substring(0, result.lastIndexOf('/'));
+ if ("folder".equals(FrameworkProperties.getProperty(PROP_FRAMEWORK_SHAPE))) //$NON-NLS-1$
+ result = result.substring(0, result.lastIndexOf('/'));
+ } else {
+ if (result.endsWith("/")) //$NON-NLS-1$
+ result = result.substring(0, result.length() - 1);
+ result = result.substring(0, result.lastIndexOf('/'));
+ result = result.substring(0, result.lastIndexOf('/'));
+ }
+ return result;
+ }
+
+ private static Bundle[] getCurrentBundles(boolean includeInitial) {
+ Bundle[] installed = context.getBundles();
+ List<Bundle> initial = new ArrayList<Bundle>();
+ for (int i = 0; i < installed.length; i++) {
+ Bundle bundle = installed[i];
+ if (bundle.getLocation().startsWith(INITIAL_LOCATION)) {
+ if (includeInitial)
+ initial.add(bundle);
+ } else if (!includeInitial && bundle.getBundleId() != 0)
+ initial.add(bundle);
+ }
+ return initial.toArray(new Bundle[initial.size()]);
+ }
+
+ private static Bundle getBundleByLocation(String location, Bundle[] bundles) {
+ for (int i = 0; i < bundles.length; i++) {
+ Bundle bundle = bundles[i];
+ if (location.equalsIgnoreCase(bundle.getLocation()))
+ return bundle;
+ }
+ return null;
+ }
+
+ private static void uninstallBundles(Bundle[] curInitBundles, InitialBundle[] newInitBundles, List<Bundle> toRefresh) {
+ for (int i = 0; i < curInitBundles.length; i++) {
+ boolean found = false;
+ for (int j = 0; j < newInitBundles.length; j++) {
+ if (curInitBundles[i].getLocation().equalsIgnoreCase(newInitBundles[j].locationString)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ try {
+ curInitBundles[i].uninstall();
+ toRefresh.add(curInitBundles[i]);
+ } catch (BundleException e) {
+ FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_FAILED_UNINSTALL, curInitBundles[i].getLocation()), 0, e, null);
+ log.log(entry);
+ }
+ }
+ }
+
+ private static void installBundles(InitialBundle[] initialBundles, Bundle[] curInitBundles, List<Bundle> startBundles, List<Bundle> lazyActivationBundles, List<Bundle> toRefresh) {
+ ServiceReference<?> reference = context.getServiceReference(StartLevel.class.getName());
+ StartLevel startService = null;
+ if (reference != null)
+ startService = (StartLevel) context.getService(reference);
+ try {
+ for (int i = 0; i < initialBundles.length; i++) {
+ Bundle osgiBundle = getBundleByLocation(initialBundles[i].locationString, curInitBundles);
+ try {
+ // don't need to install if it is already installed
+ if (osgiBundle == null) {
+ InputStream in = initialBundles[i].location.openStream();
+ try {
+ osgiBundle = context.installBundle(initialBundles[i].locationString, in);
+ } catch (BundleException e) {
+ StatusException status = e instanceof StatusException ? (StatusException) e : null;
+ if (status != null && status.getStatusCode() == StatusException.CODE_OK && status.getStatus() instanceof Bundle) {
+ osgiBundle = (Bundle) status.getStatus();
+ } else
+ throw e;
+ }
+ // only check for lazy activation header if this is a newly installed bundle and is not marked for persistent start
+ if (!initialBundles[i].start && hasLazyActivationPolicy(osgiBundle))
+ lazyActivationBundles.add(osgiBundle);
+ }
+ // always set the startlevel incase it has changed (bug 111549)
+ // this is a no-op if the level is the same as previous launch.
+ if ((osgiBundle.getState() & Bundle.UNINSTALLED) == 0 && initialBundles[i].level >= 0 && startService != null)
+ startService.setBundleStartLevel(osgiBundle, initialBundles[i].level);
+ // if this bundle is supposed to be started then add it to the start list
+ if (initialBundles[i].start)
+ startBundles.add(osgiBundle);
+ // include basic bundles in case they were not resolved before
+ if ((osgiBundle.getState() & Bundle.INSTALLED) != 0)
+ toRefresh.add(osgiBundle);
+ } catch (BundleException e) {
+ FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_FAILED_INSTALL, initialBundles[i].location), 0, e, null);
+ log.log(entry);
+ } catch (IOException e) {
+ FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_FAILED_INSTALL, initialBundles[i].location), 0, e, null);
+ log.log(entry);
+ }
+ }
+ } finally {
+ if (reference != null)
+ context.ungetService(reference);
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ private static boolean hasLazyActivationPolicy(Bundle target) {
+ // check the bundle manifest to see if it defines a lazy activation policy
+ Dictionary<String, String> headers = target.getHeaders(""); //$NON-NLS-1$
+ // first check to see if this is a fragment bundle
+ String fragmentHost = headers.get(Constants.FRAGMENT_HOST);
+ if (fragmentHost != null)
+ return false; // do not activate fragment bundles
+ // look for the OSGi defined Bundle-ActivationPolicy header
+ String activationPolicy = headers.get(Constants.BUNDLE_ACTIVATIONPOLICY);
+ try {
+ if (activationPolicy != null) {
+ ManifestElement[] elements = ManifestElement.parseHeader(Constants.BUNDLE_ACTIVATIONPOLICY, activationPolicy);
+ if (elements != null && elements.length > 0) {
+ // if the value is "lazy" then it has a lazy activation poliyc
+ if (Constants.ACTIVATION_LAZY.equals(elements[0].getValue()))
+ return true;
+ }
+ } else {
+ // check for Eclipse specific lazy start headers "Eclipse-LazyStart" and "Eclipse-AutoStart"
+ String eclipseLazyStart = headers.get(Constants.ECLIPSE_LAZYSTART);
+ if (eclipseLazyStart == null)
+ eclipseLazyStart = headers.get(Constants.ECLIPSE_AUTOSTART);
+ ManifestElement[] elements = ManifestElement.parseHeader(Constants.ECLIPSE_LAZYSTART, eclipseLazyStart);
+ if (elements != null && elements.length > 0) {
+ // if the value is true then it is lazy activated
+ if ("true".equals(elements[0].getValue())) //$NON-NLS-1$
+ return true;
+ // otherwise it is only lazy activated if it defines an exceptions directive.
+ else if (elements[0].getDirective("exceptions") != null) //$NON-NLS-1$
+ return true;
+ }
+ }
+ } catch (BundleException be) {
+ // ignore this
+ }
+ return false;
+ }
+
+ private static void startBundles(Bundle[] startBundles, Bundle[] lazyBundles) {
+ for (int i = 0; i < startBundles.length; i++)
+ startBundle(startBundles[i], 0);
+ for (int i = 0; i < lazyBundles.length; i++)
+ startBundle(lazyBundles[i], Bundle.START_ACTIVATION_POLICY);
+ }
+
+ private static void startBundle(Bundle bundle, int options) {
+ try {
+ bundle.start(options);
+ } catch (BundleException e) {
+ if ((bundle.getState() & Bundle.RESOLVED) != 0) {
+ // only log errors if the bundle is resolved
+ FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_FAILED_START, bundle.getLocation()), 0, e, null);
+ log.log(entry);
+ }
+ }
+ }
+
+ private static void loadConfigurationInfo() {
+ Location configArea = LocationManager.getConfigurationLocation();
+ if (configArea == null)
+ return;
+
+ URL location = null;
+ try {
+ location = new URL(configArea.getURL().toExternalForm() + LocationManager.CONFIG_FILE);
+ } catch (MalformedURLException e) {
+ // its ok. This should never happen
+ }
+ mergeProperties(FrameworkProperties.getProperties(), loadProperties(location));
+ }
+
+ private static Properties loadProperties(URL location) {
+ Properties result = new Properties();
+ if (location == null)
+ return result;
+ try {
+ InputStream in = location.openStream();
+ try {
+ result.load(in);
+ } finally {
+ in.close();
+ }
+ } catch (IOException e) {
+ // its ok if there is no file. We'll just use the defaults for everything
+ // TODO but it might be nice to log something with gentle wording (i.e., it is not an error)
+ }
+ return substituteVars(result);
+ }
+
+ private static Properties substituteVars(Properties result) {
+ if (result == null) {
+ //nothing todo.
+ return null;
+ }
+ for (Enumeration<Object> eKeys = result.keys(); eKeys.hasMoreElements();) {
+ Object key = eKeys.nextElement();
+ if (key instanceof String) {
+ String value = result.getProperty((String) key);
+ if (value != null)
+ result.put(key, BaseStorageHook.substituteVars(value));
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns a URL which is equivalent to the given URL relative to the
+ * specified base URL. Works only for file: URLs
+ * @throws MalformedURLException
+ */
+ private static URL makeRelative(URL base, URL location) throws MalformedURLException {
+ if (base == null)
+ return location;
+ if (!"file".equals(base.getProtocol())) //$NON-NLS-1$
+ return location;
+ if (!location.getProtocol().equals(REFERENCE_PROTOCOL))
+ return location; // we can only make reference urls relative
+ URL nonReferenceLocation = new URL(location.getPath());
+ // if some URL component does not match, return the original location
+ if (!base.getProtocol().equals(nonReferenceLocation.getProtocol()))
+ return location;
+ File locationPath = new File(nonReferenceLocation.getPath());
+ // if location is not absolute, return original location
+ if (!locationPath.isAbsolute())
+ return location;
+ File relativePath = makeRelative(new File(base.getPath()), locationPath);
+ String urlPath = relativePath.getPath();
+ if (File.separatorChar != '/')
+ urlPath = urlPath.replace(File.separatorChar, '/');
+ if (nonReferenceLocation.getPath().endsWith("/")) //$NON-NLS-1$
+ // restore original trailing slash
+ urlPath += '/';
+ // couldn't use File to create URL here because it prepends the path with user.dir
+ URL relativeURL = new URL(base.getProtocol(), base.getHost(), base.getPort(), urlPath);
+ // now make it back to a reference URL
+ relativeURL = new URL(REFERENCE_SCHEME + relativeURL.toExternalForm());
+ return relativeURL;
+ }
+
+ private static File makeRelative(File base, File location) {
+ if (!location.isAbsolute())
+ return location;
+ File relative = new File(new FilePath(base).makeRelative(new FilePath(location)));
+ return relative;
+ }
+
+ private static void mergeProperties(Properties destination, Properties source) {
+ for (Enumeration<?> e = source.keys(); e.hasMoreElements();) {
+ String key = (String) e.nextElement();
+ String value = source.getProperty(key);
+ if (destination.getProperty(key) == null)
+ destination.setProperty(key, value);
+ }
+ }
+
+ private static void setStartLevel(final int value) {
+ ServiceReference<?> reference = context.getServiceReference(StartLevel.class.getName());
+ final StartLevel startLevel = reference != null ? (StartLevel) context.getService(reference) : null;
+ if (startLevel == null)
+ return;
+ final Semaphore semaphore = new Semaphore(0);
+ StartupEventListener listener = new StartupEventListener(semaphore, FrameworkEvent.STARTLEVEL_CHANGED);
+ context.addFrameworkListener(listener);
+ context.addBundleListener(listener);
+ startLevel.setStartLevel(value);
+ context.ungetService(reference);
+ updateSplash(semaphore, listener);
+ }
+
+ static class StartupEventListener implements SynchronousBundleListener, FrameworkListener {
+ private final Semaphore semaphore;
+ private final int frameworkEventType;
+
+ public StartupEventListener(Semaphore semaphore, int frameworkEventType) {
+ this.semaphore = semaphore;
+ this.frameworkEventType = frameworkEventType;
+ }
+
+ public void bundleChanged(BundleEvent event) {
+ if (event.getBundle().getBundleId() == 0 && event.getType() == BundleEvent.STOPPING)
+ semaphore.release();
+ }
+
+ public void frameworkEvent(FrameworkEvent event) {
+ if (event.getType() == frameworkEventType)
+ semaphore.release();
+ }
+
+ }
+
+ private static void updateSplash(Semaphore semaphore, StartupEventListener listener) {
+ ServiceTracker<StartupMonitor, StartupMonitor> monitorTracker = new ServiceTracker<StartupMonitor, StartupMonitor>(context, StartupMonitor.class.getName(), null);
+ monitorTracker.open();
+ try {
+ while (true) {
+ StartupMonitor monitor = monitorTracker.getService();
+ if (monitor != null) {
+ try {
+ monitor.update();
+ } catch (Throwable e) {
+ // ignore exceptions thrown by the monitor
+ }
+ }
+ // can we acquire the semaphore yet?
+ if (semaphore.acquire(50))
+ break; //done
+ //else still working, spin another update
+ }
+ } finally {
+ if (listener != null) {
+ context.removeFrameworkListener(listener);
+ context.removeBundleListener(listener);
+ }
+ monitorTracker.close();
+ }
+ }
+
+ /**
+ * Searches for the given target directory immediately under
+ * the given start location. If one is found then this location is returned;
+ * otherwise an exception is thrown.
+ *
+ * @return the location where target directory was found
+ * @param start the location to begin searching
+ */
+ private static String searchFor(final String target, String start) {
+ String[] candidates = searchCandidates.get(start);
+ if (candidates == null) {
+ File startFile = new File(start);
+ // Pre-check if file exists, if not, and it contains escape characters,
+ // try decoding the path
+ if (!startFile.exists() && start.indexOf('%') >= 0) {
+ String decodePath = FrameworkProperties.decode(start);
+ File f = new File(decodePath);
+ if (f.exists())
+ startFile = f;
+ }
+ candidates = startFile.list();
+ if (candidates != null)
+ searchCandidates.put(start, candidates);
+ }
+ if (candidates == null)
+ return null;
+ String result = null;
+ Object[] maxVersion = null;
+ boolean resultIsFile = false;
+ for (int i = 0; i < candidates.length; i++) {
+ String candidateName = candidates[i];
+ if (!candidateName.startsWith(target))
+ continue;
+ boolean simpleJar = false;
+ final char versionSep = candidateName.length() > target.length() ? candidateName.charAt(target.length()) : 0;
+ if (candidateName.length() > target.length() && versionSep != '_' && versionSep != '-') {
+ // make sure this is not just a jar with no (_|-)version tacked on the end
+ if (candidateName.length() == 4 + target.length() && candidateName.endsWith(".jar")) //$NON-NLS-1$
+ simpleJar = true;
+ else
+ // name does not match the target properly with an (_|-) version at the end
+ continue;
+ }
+ // Note: directory with version suffix is always > than directory without version suffix
+ String version = candidateName.length() > target.length() + 1 && (versionSep == '_' || versionSep == '-') ? candidateName.substring(target.length() + 1) : ""; //$NON-NLS-1$
+ Object[] currentVersion = getVersionElements(version);
+ if (currentVersion != null && compareVersion(maxVersion, currentVersion) < 0) {
+ File candidate = new File(start, candidateName);
+ boolean candidateIsFile = candidate.isFile();
+ // if simple jar; make sure it is really a file before accepting it
+ if (!simpleJar || candidateIsFile) {
+ result = candidate.getAbsolutePath();
+ resultIsFile = candidateIsFile;
+ maxVersion = currentVersion;
+ }
+ }
+ }
+ if (result == null)
+ return null;
+ return result.replace(File.separatorChar, '/') + (resultIsFile ? "" : "/"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ /**
+ * Do a quick parse of version identifier so its elements can be correctly compared.
+ * If we are unable to parse the full version, remaining elements are initialized
+ * with suitable defaults.
+ * @return an array of size 4; first three elements are of type Integer (representing
+ * major, minor and service) and the fourth element is of type String (representing
+ * qualifier). A value of null is returned if there are no valid Integers. Note, that
+ * returning anything else will cause exceptions in the caller.
+ */
+ private static Object[] getVersionElements(String version) {
+ Object[] result = {new Integer(-1), new Integer(-1), new Integer(-1), ""}; //$NON-NLS-1$
+ StringTokenizer t = new StringTokenizer(version, "."); //$NON-NLS-1$
+ String token;
+ for (int i = 0; t.hasMoreTokens() && i < 4; i++) {
+ token = t.nextToken();
+ if (i < 3) {
+ // major, minor or service ... numeric values
+ try {
+ result[i] = new Integer(token);
+ } catch (Exception e) {
+ if (i == 0)
+ return null; // return null if no valid numbers are present
+ // invalid number format - use default numbers (-1) for the rest
+ break;
+ }
+ } else {
+ // qualifier ... string value
+ result[i] = token;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Compares version strings.
+ * @return result of comparison, as integer;
+ * <code><0</code> if left < right;
+ * <code>0</code> if left == right;
+ * <code>>0</code> if left > right;
+ */
+ private static int compareVersion(Object[] left, Object[] right) {
+ if (left == null)
+ return -1;
+ int result = ((Integer) left[0]).compareTo((Integer) right[0]); // compare major
+ if (result != 0)
+ return result;
+
+ result = ((Integer) left[1]).compareTo((Integer) right[1]); // compare minor
+ if (result != 0)
+ return result;
+
+ result = ((Integer) left[2]).compareTo((Integer) right[2]); // compare service
+ if (result != 0)
+ return result;
+
+ return ((String) left[3]).compareTo((String) right[3]); // compare qualifier
+ }
+
+ private static void finalizeProperties() {
+ // if check config is unknown and we are in dev mode,
+ if (FrameworkProperties.getProperty(PROP_DEV) != null && FrameworkProperties.getProperty(PROP_CHECK_CONFIG) == null)
+ FrameworkProperties.setProperty(PROP_CHECK_CONFIG, "true"); //$NON-NLS-1$
+ }
+
+ private static class InitialBundle {
+ public final String locationString;
+ public final URL location;
+ public final int level;
+ public final boolean start;
+
+ InitialBundle(String locationString, URL location, int level, boolean start) {
+ this.locationString = locationString;
+ this.location = location;
+ this.level = level;
+ this.start = start;
+ }
+ }
+
+ /**
+ * Sets the initial properties for the platform.
+ * This method must be called before calling the {@link #run(String[], Runnable)} or
+ * {@link #startup(String[], Runnable)} methods for the properties to be used in
+ * a launched instance of the platform.
+ * <p>
+ * If the specified properties contains a null value then the key for that value
+ * will be cleared from the properties of the platform.
+ * </p>
+ * @param initialProperties the initial properties to set for the platform.
+ * @since 3.2
+ */
+ public static void setInitialProperties(Map<String, String> initialProperties) {
+ if (initialProperties == null || initialProperties.isEmpty())
+ return;
+ for (Map.Entry<String, String> entry : initialProperties.entrySet()) {
+ if (entry.getValue() != null)
+ FrameworkProperties.setProperty(entry.getKey(), entry.getValue());
+ else
+ FrameworkProperties.clearProperty(entry.getKey());
+ }
+ }
+
+ /**
+ * Returns the context of the system bundle. A value of
+ * <code>null</code> is returned if the platform is not running.
+ * @return the context of the system bundle
+ * @throws java.lang.SecurityException If the caller does not have the
+ * appropriate <code>AdminPermission[system.bundle,CONTEXT]</code>, and
+ * the Java Runtime Environment supports permissions.
+ */
+ public static BundleContext getSystemBundleContext() {
+ if (context == null || !running)
+ return null;
+ return context.getBundle().getBundleContext();
+ }
+
+ private static boolean isForcedRestart() {
+ return Boolean.valueOf(FrameworkProperties.getProperty(PROP_FORCED_RESTART)).booleanValue();
+ }
+
+ /*
+ * NOTE: This is an internal/experimental method used by launchers that need to react when the framework
+ * is shutdown internally.
+ *
+ * Adds a framework shutdown handler. <p>
+ * A handler implements the {@link Runnable} interface. When the framework is shutdown
+ * the {@link Runnable#run()} method is called for each registered handler. Handlers should
+ * make no assumptions on the thread it is being called from. If a handler object is
+ * registered multiple times it will be called once for each registration.
+ * <p>
+ * At the time a handler is called the framework is shutdown. Handlers must not depend on
+ * a running framework to execute or attempt to load additional classes from bundles
+ * installed in the framework.
+ * @param handler the framework shutdown handler
+ * @throws IllegalStateException if the platform is already running
+ */
+ static void internalAddFrameworkShutdownHandler(Runnable handler) {
+ if (running)
+ throw new IllegalStateException(EclipseAdaptorMsg.ECLIPSE_STARTUP_ALREADY_RUNNING);
+
+ if (shutdownHandlers == null)
+ shutdownHandlers = new ArrayList<Runnable>();
+
+ shutdownHandlers.add(handler);
+ }
+
+ /*
+ * NOTE: This is an internal/experimental method used by launchers that need to react when the framework
+ * is shutdown internally.
+ *
+ * Removes a framework shutdown handler. <p>
+ * @param handler the framework shutdown handler
+ * @throws IllegalStateException if the platform is already running
+ */
+ static void internalRemoveFrameworkShutdownHandler(Runnable handler) {
+ if (running)
+ throw new IllegalStateException(EclipseAdaptorMsg.ECLIPSE_STARTUP_ALREADY_RUNNING);
+
+ if (shutdownHandlers != null)
+ shutdownHandlers.remove(handler);
+ }
+
+ private static void registerFrameworkShutdownHandlers() {
+ if (shutdownHandlers == null)
+ return;
+
+ final Bundle systemBundle = context.getBundle();
+ for (Iterator<Runnable> it = shutdownHandlers.iterator(); it.hasNext();) {
+ final Runnable handler = it.next();
+ BundleListener listener = new BundleListener() {
+ public void bundleChanged(BundleEvent event) {
+ if (event.getBundle() == systemBundle && event.getType() == BundleEvent.STOPPED) {
+ handler.run();
+ }
+ }
+ };
+ context.addBundleListener(listener);
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/adaptor/LocationManager.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/adaptor/LocationManager.java
new file mode 100644
index 000000000..2e06c32fc
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/adaptor/LocationManager.java
@@ -0,0 +1,415 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.runtime.adaptor;
+
+import java.io.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Properties;
+import org.eclipse.core.runtime.internal.adaptor.*;
+import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor;
+import org.eclipse.osgi.framework.internal.core.Constants;
+import org.eclipse.osgi.framework.internal.core.FrameworkProperties;
+import org.eclipse.osgi.internal.baseadaptor.AdaptorUtil;
+import org.eclipse.osgi.service.datalocation.Location;
+
+/**
+ * This class is used to manage the various Locations for Eclipse.
+ * <p>
+ * Clients may not extend this class.
+ * </p>
+ * @since 3.1
+ * @noextend This class is not intended to be subclassed by clients.
+ */
+public class LocationManager {
+ private static Location installLocation = null;
+ private static Location configurationLocation = null;
+ private static Location userLocation = null;
+ private static Location instanceLocation = null;
+ private static Location eclipseHomeLocation = null;
+
+ public static final String READ_ONLY_AREA_SUFFIX = ".readOnly"; //$NON-NLS-1$
+ public static final String PROP_INSTALL_AREA = "osgi.install.area"; //$NON-NLS-1$
+ public static final String PROP_CONFIG_AREA = "osgi.configuration.area"; //$NON-NLS-1$
+ public static final String PROP_CONFIG_AREA_DEFAULT = "osgi.configuration.area.default"; //$NON-NLS-1$
+ public static final String PROP_SHARED_CONFIG_AREA = "osgi.sharedConfiguration.area"; //$NON-NLS-1$
+ public static final String PROP_INSTANCE_AREA = "osgi.instance.area"; //$NON-NLS-1$
+ public static final String PROP_INSTANCE_AREA_DEFAULT = "osgi.instance.area.default"; //$NON-NLS-1$
+ public static final String PROP_USER_AREA = "osgi.user.area"; //$NON-NLS-1$
+ public static final String PROP_USER_AREA_DEFAULT = "osgi.user.area.default"; //$NON-NLS-1$
+ public static final String PROP_MANIFEST_CACHE = "osgi.manifest.cache"; //$NON-NLS-1$
+ public static final String PROP_USER_HOME = "user.home"; //$NON-NLS-1$
+ public static final String PROP_USER_DIR = "user.dir"; //$NON-NLS-1$
+ public static final String PROP_HOME_LOCATION_AREA = "eclipse.home.location"; //$NON-NLS-1$
+ static final String PROP_LAUNCHER = "eclipse.launcher"; //$NON-NLS-1$
+
+ // configuration area file/dir names
+ public static final String BUNDLES_DIR = "bundles"; //$NON-NLS-1$
+ public static final String STATE_FILE = ".state"; //$NON-NLS-1$
+ public static final String LAZY_FILE = ".lazy"; //$NON-NLS-1$
+ public static final String BUNDLE_DATA_FILE = ".bundledata"; //$NON-NLS-1$
+ public static final String MANIFESTS_DIR = "manifests"; //$NON-NLS-1$
+ public static final String CONFIG_FILE = "config.ini"; //$NON-NLS-1$
+ public static final String ECLIPSE_PROPERTIES = "eclipse.properties"; //$NON-NLS-1$
+
+ // Constants for configuration location discovery
+ private static final String ECLIPSE = "eclipse"; //$NON-NLS-1$
+ private static final String PRODUCT_SITE_MARKER = ".eclipseproduct"; //$NON-NLS-1$
+ private static final String PRODUCT_SITE_ID = "id"; //$NON-NLS-1$
+ private static final String PRODUCT_SITE_VERSION = "version"; //$NON-NLS-1$
+
+ private static final String CONFIG_DIR = "configuration"; //$NON-NLS-1$
+
+ // Data mode constants for user, configuration and data locations.
+ private static final String NONE = "@none"; //$NON-NLS-1$
+ private static final String NO_DEFAULT = "@noDefault"; //$NON-NLS-1$
+ private static final String USER_HOME = "@user.home"; //$NON-NLS-1$
+ private static final String USER_DIR = "@user.dir"; //$NON-NLS-1$
+ // Placeholder for hashcode of installation directory
+ private static final String INSTALL_HASH_PLACEHOLDER = "@install.hash"; //$NON-NLS-1$
+
+ private static final String INSTANCE_DATA_AREA_PREFIX = ".metadata/.plugins/"; //$NON-NLS-1$
+
+ /**
+ * Builds a URL with the given specification
+ * @param spec the URL specification
+ * @param trailingSlash flag to indicate a trailing slash on the spec
+ * @return a URL
+ */
+ public static URL buildURL(String spec, boolean trailingSlash) {
+ return LocationHelper.buildURL(spec, trailingSlash);
+ }
+
+ private static void mungeConfigurationLocation() {
+ // if the config property was set, munge it for backwards compatibility.
+ String location = FrameworkProperties.getProperty(PROP_CONFIG_AREA);
+ if (location != null) {
+ if (location.endsWith(".cfg")) { //$NON-NLS-1$
+ int index = location.lastIndexOf('/');
+ if (index < 0)
+ index = location.lastIndexOf('\\');
+ location = location.substring(0, index + 1);
+ FrameworkProperties.setProperty(PROP_CONFIG_AREA, location);
+ }
+ }
+ }
+
+ /**
+ * Initializes the Location objects for the LocationManager.
+ */
+ public static void initializeLocations() {
+ // set the osgi storage area if it exists
+ String osgiStorage = FrameworkProperties.getProperty(Constants.FRAMEWORK_STORAGE);
+ if (osgiStorage != null)
+ FrameworkProperties.setProperty(PROP_CONFIG_AREA, osgiStorage);
+ // do install location initialization first since others may depend on it
+ // assumes that the property is already set
+ installLocation = buildLocation(PROP_INSTALL_AREA, null, "", true, false, null); //$NON-NLS-1$
+
+ // TODO not sure what the data area prefix should be here for the user area
+ Location temp = buildLocation(PROP_USER_AREA_DEFAULT, null, "", false, false, null); //$NON-NLS-1$
+ URL defaultLocation = temp == null ? null : temp.getURL();
+ if (defaultLocation == null)
+ defaultLocation = buildURL(new File(FrameworkProperties.getProperty(PROP_USER_HOME), "user").getAbsolutePath(), true); //$NON-NLS-1$
+ userLocation = buildLocation(PROP_USER_AREA, defaultLocation, "", false, false, null); //$NON-NLS-1$
+
+ temp = buildLocation(PROP_INSTANCE_AREA_DEFAULT, null, "", false, false, INSTANCE_DATA_AREA_PREFIX); //$NON-NLS-1$
+ defaultLocation = temp == null ? null : temp.getURL();
+ if (defaultLocation == null)
+ defaultLocation = buildURL(new File(FrameworkProperties.getProperty(PROP_USER_DIR), "workspace").getAbsolutePath(), true); //$NON-NLS-1$
+ instanceLocation = buildLocation(PROP_INSTANCE_AREA, defaultLocation, "", false, false, INSTANCE_DATA_AREA_PREFIX); //$NON-NLS-1$
+
+ mungeConfigurationLocation();
+ // compute a default but it is very unlikely to be used since main will have computed everything
+ temp = buildLocation(PROP_CONFIG_AREA_DEFAULT, null, "", false, false, null); //$NON-NLS-1$
+ defaultLocation = temp == null ? null : temp.getURL();
+ if (defaultLocation == null && FrameworkProperties.getProperty(PROP_CONFIG_AREA) == null)
+ // only compute the default if the configuration area property is not set
+ defaultLocation = buildURL(computeDefaultConfigurationLocation(), true);
+ configurationLocation = buildLocation(PROP_CONFIG_AREA, defaultLocation, "", false, false, null); //$NON-NLS-1$
+ // get the parent location based on the system property. This will have been set on the
+ // way in either by the caller/user or by main. There will be no parent location if we are not
+ // cascaded.
+ URL parentLocation = computeSharedConfigurationLocation();
+ if (parentLocation != null && !parentLocation.equals(configurationLocation.getURL())) {
+ Location parent = new BasicLocation(null, parentLocation, true, null);
+ ((BasicLocation) configurationLocation).setParent(parent);
+ }
+ initializeDerivedConfigurationLocations();
+
+ if (FrameworkProperties.getProperty(PROP_HOME_LOCATION_AREA) == null) {
+ String eclipseLauncher = FrameworkProperties.getProperty(PROP_LAUNCHER);
+ String eclipseHomeLocationPath = getEclipseHomeLocation(eclipseLauncher);
+ if (eclipseHomeLocationPath != null)
+ FrameworkProperties.setProperty(PROP_HOME_LOCATION_AREA, eclipseHomeLocationPath);
+ }
+ // if eclipse.home.location is not set then default to osgi.install.area
+ if (FrameworkProperties.getProperty(PROP_HOME_LOCATION_AREA) == null && FrameworkProperties.getProperty(PROP_INSTALL_AREA) != null)
+ FrameworkProperties.setProperty(PROP_HOME_LOCATION_AREA, FrameworkProperties.getProperty(PROP_INSTALL_AREA));
+ eclipseHomeLocation = buildLocation(PROP_HOME_LOCATION_AREA, null, "", true, true, null); //$NON-NLS-1$
+ }
+
+ private static String getEclipseHomeLocation(String launcher) {
+ if (launcher == null)
+ return null;
+ File launcherFile = new File(launcher);
+ if (launcherFile.getParent() == null)
+ return null;
+ File launcherDir = new File(launcherFile.getParent());
+ // check for mac os; the os check is copied from EclipseEnvironmentInfo.
+ String macosx = org.eclipse.osgi.service.environment.Constants.OS_MACOSX;
+ if (macosx.equals(EclipseEnvironmentInfo.getDefault().getOS()))
+ launcherDir = getMacOSEclipsoeHomeLocation(launcherDir);
+ return (launcherDir.exists() && launcherDir.isDirectory()) ? launcherDir.getAbsolutePath() : null;
+ }
+
+ private static File getMacOSEclipsoeHomeLocation(File launcherDir) {
+ // TODO for now we go up three directories from the launcher dir as long as the parent dir is named MacOS; is this always the case?
+ // TODO not sure if case is important
+ if (!launcherDir.getName().equalsIgnoreCase("macos")) //$NON-NLS-1$
+ return launcherDir; // don't do the up three stuff if not in macos directory
+ String launcherParent = launcherDir.getParent();
+ if (launcherParent != null)
+ launcherParent = new File(launcherParent).getParent();
+ if (launcherParent != null)
+ launcherParent = new File(launcherParent).getParent();
+ return launcherParent == null ? null : new File(launcherParent);
+ }
+
+ @SuppressWarnings("deprecation")
+ private static Location buildLocation(String property, URL defaultLocation, String userDefaultAppendage, boolean readOnlyDefault, boolean computeReadOnly, String dataAreaPrefix) {
+ String location = FrameworkProperties.clearProperty(property);
+ // the user/product may specify a non-default readOnly setting
+ String userReadOnlySetting = FrameworkProperties.getProperty(property + READ_ONLY_AREA_SUFFIX);
+ boolean readOnly = (userReadOnlySetting == null ? readOnlyDefault : Boolean.valueOf(userReadOnlySetting).booleanValue());
+ // if the instance location is not set, predict where the workspace will be and
+ // put the instance area inside the workspace meta area.
+ if (location == null)
+ return new BasicLocation(property, defaultLocation, userReadOnlySetting != null || !computeReadOnly ? readOnly : !canWrite(defaultLocation), dataAreaPrefix);
+ String trimmedLocation = location.trim();
+ if (trimmedLocation.equalsIgnoreCase(NONE))
+ return null;
+ if (trimmedLocation.equalsIgnoreCase(NO_DEFAULT))
+ return new BasicLocation(property, null, readOnly, dataAreaPrefix);
+ if (trimmedLocation.startsWith(USER_HOME)) {
+ String base = substituteVar(location, USER_HOME, PROP_USER_HOME);
+ location = new File(base, userDefaultAppendage).getAbsolutePath();
+ } else if (trimmedLocation.startsWith(USER_DIR)) {
+ String base = substituteVar(location, USER_DIR, PROP_USER_DIR);
+ location = new File(base, userDefaultAppendage).getAbsolutePath();
+ }
+ int idx = location.indexOf(INSTALL_HASH_PLACEHOLDER);
+ if (idx == 0) {
+ throw new RuntimeException("The location cannot start with '" + INSTALL_HASH_PLACEHOLDER + "': " + location); //$NON-NLS-1$ //$NON-NLS-2$
+ } else if (idx > 0) {
+ location = location.substring(0, idx) + getInstallDirHash() + location.substring(idx + INSTALL_HASH_PLACEHOLDER.length());
+ }
+ URL url = buildURL(location, true);
+ BasicLocation result = null;
+ if (url != null) {
+ result = new BasicLocation(property, null, userReadOnlySetting != null || !computeReadOnly ? readOnly : !canWrite(url), dataAreaPrefix);
+ result.setURL(url, false);
+ }
+ return result;
+ }
+
+ private static String substituteVar(String source, String var, String prop) {
+ String value = FrameworkProperties.getProperty(prop, ""); //$NON-NLS-1$
+ return value + source.substring(var.length());
+ }
+
+ private static void initializeDerivedConfigurationLocations() {
+ if (FrameworkProperties.getProperty(PROP_MANIFEST_CACHE) == null)
+ FrameworkProperties.setProperty(PROP_MANIFEST_CACHE, getConfigurationFile(MANIFESTS_DIR).getAbsolutePath());
+ }
+
+ private static URL computeInstallConfigurationLocation() {
+ String property = FrameworkProperties.getProperty(PROP_INSTALL_AREA);
+ if (property != null)
+ return LocationHelper.buildURL(property, true);
+ return null;
+ }
+
+ private static URL computeSharedConfigurationLocation() {
+ String property = FrameworkProperties.getProperty(PROP_SHARED_CONFIG_AREA);
+ if (property == null)
+ return null;
+ try {
+ URL sharedConfigurationURL = LocationHelper.buildURL(property, true);
+ if (sharedConfigurationURL == null)
+ return null;
+ if (sharedConfigurationURL.getPath().startsWith("/")) //$NON-NLS-1$
+ // absolute
+ return sharedConfigurationURL;
+ URL installURL = installLocation.getURL();
+ if (!sharedConfigurationURL.getProtocol().equals(installURL.getProtocol()))
+ // different protocol
+ return sharedConfigurationURL;
+ sharedConfigurationURL = new URL(installURL, sharedConfigurationURL.getPath());
+ FrameworkProperties.setProperty(PROP_SHARED_CONFIG_AREA, sharedConfigurationURL.toExternalForm());
+ } catch (MalformedURLException e) {
+ // do nothing here since it is basically impossible to get a bogus url
+ }
+ return null;
+ }
+
+ private static String computeDefaultConfigurationLocation() {
+ // 1) We store the config state relative to the 'eclipse' directory if possible
+ // 2) If this directory is read-only
+ // we store the state in <user.home>/.eclipse/<application-id>_<version> where <user.home>
+ // is unique for each local user, and <application-id> is the one
+ // defined in .eclipseproduct marker file. If .eclipseproduct does not
+ // exist, use "eclipse" as the application-id.
+
+ URL installURL = computeInstallConfigurationLocation();
+ if (installURL != null && "file".equals(installURL.getProtocol())) { //$NON-NLS-1$
+ File installDir = new File(installURL.getFile());
+ File defaultConfigDir = new File(installDir, CONFIG_DIR);
+ if (!defaultConfigDir.exists())
+ defaultConfigDir.mkdirs();
+ if (defaultConfigDir.exists() && AdaptorUtil.canWrite(defaultConfigDir))
+ return defaultConfigDir.getAbsolutePath();
+ }
+ // We can't write in the eclipse install dir so try for some place in the user's home dir
+ return computeDefaultUserAreaLocation(CONFIG_DIR);
+ }
+
+ private static boolean canWrite(URL location) {
+ if (location != null && "file".equals(location.getProtocol())) { //$NON-NLS-1$
+ File locationDir = new File(location.getFile());
+ if (!locationDir.exists())
+ locationDir.mkdirs();
+ if (locationDir.exists() && AdaptorUtil.canWrite(locationDir))
+ return true;
+ }
+ return false;
+ }
+
+ private static String computeDefaultUserAreaLocation(String pathAppendage) {
+ // we store the state in <user.home>/.eclipse/<application-id>_<version> where <user.home>
+ // is unique for each local user, and <application-id> is the one
+ // defined in .eclipseproduct marker file. If .eclipseproduct does not
+ // exist, use "eclipse" as the application-id.
+ String installProperty = FrameworkProperties.getProperty(PROP_INSTALL_AREA);
+ URL installURL = buildURL(installProperty, true);
+ if (installURL == null)
+ return null;
+ File installDir = new File(installURL.getFile());
+ String installDirHash = getInstallDirHash();
+
+ String appName = "." + ECLIPSE; //$NON-NLS-1$
+ File eclipseProduct = new File(installDir, PRODUCT_SITE_MARKER);
+ if (eclipseProduct.exists()) {
+ Properties props = new Properties();
+ try {
+ props.load(new FileInputStream(eclipseProduct));
+ String appId = props.getProperty(PRODUCT_SITE_ID);
+ if (appId == null || appId.trim().length() == 0)
+ appId = ECLIPSE;
+ String appVersion = props.getProperty(PRODUCT_SITE_VERSION);
+ if (appVersion == null || appVersion.trim().length() == 0)
+ appVersion = ""; //$NON-NLS-1$
+ appName += File.separator + appId + "_" + appVersion + "_" + installDirHash; //$NON-NLS-1$ //$NON-NLS-2$
+ } catch (IOException e) {
+ // Do nothing if we get an exception. We will default to a standard location
+ // in the user's home dir.
+ // add the hash to help prevent collisions
+ appName += File.separator + installDirHash;
+ }
+ } else {
+ // add the hash to help prevent collisions
+ appName += File.separator + installDirHash;
+ }
+ String userHome = FrameworkProperties.getProperty(PROP_USER_HOME);
+ return new File(userHome, appName + "/" + pathAppendage).getAbsolutePath(); //$NON-NLS-1$
+ }
+
+ /**
+ * Return hash code identifying an absolute installation path
+ * @return hash code as String
+ */
+ private static String getInstallDirHash() {
+ // compute an install dir hash to prevent configuration area collisions with other eclipse installs
+ String installProperty = FrameworkProperties.getProperty(PROP_INSTALL_AREA);
+ URL installURL = buildURL(installProperty, true);
+ if (installURL == null)
+ return ""; //$NON-NLS-1$
+ File installDir = new File(installURL.getFile());
+ int hashCode;
+ try {
+ hashCode = installDir.getCanonicalPath().hashCode();
+ } catch (IOException ioe) {
+ // fall back to absolute path
+ hashCode = installDir.getAbsolutePath().hashCode();
+ }
+ if (hashCode < 0)
+ hashCode = -(hashCode);
+ String installDirHash = String.valueOf(hashCode);
+ return installDirHash;
+ }
+
+ /**
+ * Returns the user Location object
+ * @return the user Location object
+ */
+ public static Location getUserLocation() {
+ return userLocation;
+ }
+
+ /**
+ * Returns the configuration Location object
+ * @return the configuration Location object
+ */
+ public static Location getConfigurationLocation() {
+ return configurationLocation;
+ }
+
+ /**
+ * Returns the install Location object
+ * @return the install Location object
+ */
+ public static Location getInstallLocation() {
+ return installLocation;
+ }
+
+ /**
+ * Returns the instance Location object
+ * @return the instance Location object
+ */
+ public static Location getInstanceLocation() {
+ return instanceLocation;
+ }
+
+ public static Location getEclipseHomeLocation() {
+ return eclipseHomeLocation;
+ }
+
+ /**
+ * Returns the File object under the configuration location used for the OSGi configuration
+ * @return the OSGi configuration directory
+ */
+ public static File getOSGiConfigurationDir() {
+ // TODO assumes the URL is a file: url
+ return new File(configurationLocation.getURL().getFile(), FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME);
+ }
+
+ /**
+ * Returns a file from the configuration area that can be used by the framework
+ * @param filename the filename
+ * @return a file from the configuration area
+ */
+ public static File getConfigurationFile(String filename) {
+ File dir = getOSGiConfigurationDir();
+ if (!dir.exists())
+ dir.mkdirs();
+ return new File(dir, filename);
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/adaptor/package.html b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/adaptor/package.html
new file mode 100644
index 000000000..65b481997
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/adaptor/package.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Package-level Javadoc</title>
+</head>
+<body>
+Provides API to start the platform.
+<h2>
+Package Specification</h2>
+This package specifies API to start the platform.
+<p>
+Clients may use the <tt>EclipseStarter</tt> loader class to start the platform. The
+<tt>EclipseStarter</tt> class is the only defined API in this package.
+</p>
+</body>
+</html>
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/BundleLocalizationImpl.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/BundleLocalizationImpl.java
new file mode 100644
index 000000000..e2eb1d829
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/BundleLocalizationImpl.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.runtime.internal.adaptor;
+
+import java.util.ResourceBundle;
+import org.eclipse.osgi.service.localization.BundleLocalization;
+import org.osgi.framework.Bundle;
+
+/**
+ * The implementation of the service that gets ResourceBundle objects from a given
+ * bundle with a given locale.
+ *
+ * <p>Internal class.</p>
+ */
+
+public class BundleLocalizationImpl implements BundleLocalization {
+ /**
+ * The getLocalization method gets a ResourceBundle object for the given
+ * locale and bundle.
+ *
+ * @return A <code>ResourceBundle</code> object for the given bundle and locale.
+ * If null is passed for the locale parameter, the default locale is used.
+ */
+ public ResourceBundle getLocalization(Bundle bundle, String locale) {
+ return ((org.eclipse.osgi.framework.internal.core.AbstractBundle) (bundle)).getResourceBundle(locale);
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/CachedManifest.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/CachedManifest.java
new file mode 100644
index 000000000..550c2e24b
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/CachedManifest.java
@@ -0,0 +1,156 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.runtime.internal.adaptor;
+
+import java.util.Dictionary;
+import java.util.Enumeration;
+import org.eclipse.osgi.framework.adaptor.BundleData;
+import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor;
+import org.eclipse.osgi.framework.internal.core.Constants;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+import org.eclipse.osgi.framework.util.Headers;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Version;
+
+/**
+ * Internal class.
+ */
+public class CachedManifest extends Dictionary<String, String> {
+ static final String SERVICE_COMPONENT = "Service-Component"; //$NON-NLS-1$
+ static boolean DEBUG = false;
+ private Dictionary<String, String> manifest = null;
+ private EclipseStorageHook storageHook;
+
+ public CachedManifest(EclipseStorageHook storageHook) {
+ this.storageHook = storageHook;
+ }
+
+ public Dictionary<String, String> getManifest() {
+ if (manifest == null)
+ try {
+ if (DEBUG)
+ System.out.println("Reading manifest for: " + storageHook.getBaseData()); //$NON-NLS-1$
+ manifest = storageHook.createCachedManifest(true);
+ } catch (BundleException e) {
+ final String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_CACHEDMANIFEST_UNEXPECTED_EXCEPTION, storageHook.getBaseData().getLocation());
+ FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, message, 0, e, null);
+ storageHook.getAdaptor().getFrameworkLog().log(entry);
+ }
+ if (manifest == null) {
+ Headers<String, String> empty = new Headers<String, String>(0);
+ empty.setReadOnly();
+ manifest = empty;
+ return empty;
+ }
+ return manifest;
+ }
+
+ public int size() {
+ return getManifest().size();
+ }
+
+ public boolean isEmpty() {
+ return size() == 0;
+ }
+
+ public Enumeration<String> elements() {
+ return getManifest().elements();
+ }
+
+ public Enumeration<String> keys() {
+ return getManifest().keys();
+ }
+
+ @SuppressWarnings("deprecation")
+ public String get(Object key) {
+ if (manifest != null)
+ return manifest.get(key);
+ String keyString = (String) key;
+ if (Constants.BUNDLE_VERSION.equalsIgnoreCase(keyString)) {
+ Version result = storageHook.getBaseData().getVersion();
+ return result == null ? null : result.toString();
+ }
+ if (Constants.PLUGIN_CLASS.equalsIgnoreCase(keyString))
+ return storageHook.getPluginClass();
+ if (Constants.BUNDLE_SYMBOLICNAME.equalsIgnoreCase(keyString)) {
+ if ((storageHook.getBaseData().getType() & BundleData.TYPE_SINGLETON) == 0)
+ return storageHook.getBaseData().getSymbolicName();
+ return storageHook.getBaseData().getSymbolicName() + ';' + Constants.SINGLETON_DIRECTIVE + ":=true"; //$NON-NLS-1$
+ }
+ if (Constants.BUDDY_LOADER.equalsIgnoreCase(keyString))
+ return storageHook.getBuddyList();
+ if (Constants.REGISTERED_POLICY.equalsIgnoreCase(keyString))
+ return storageHook.getRegisteredBuddyList();
+ if (Constants.BUNDLE_ACTIVATOR.equalsIgnoreCase(keyString))
+ return storageHook.getBaseData().getActivator();
+ if (Constants.BUNDLE_ACTIVATIONPOLICY.equals(keyString)) {
+ if (!storageHook.isAutoStartable())
+ return null;
+ String[] excludes = storageHook.getLazyStartExcludes();
+ String[] includes = storageHook.getLazyStartIncludes();
+ if (excludes == null && includes == null)
+ return Constants.ACTIVATION_LAZY;
+ StringBuffer result = new StringBuffer(Constants.ACTIVATION_LAZY);
+ if (excludes != null) {
+ result.append(';').append(Constants.EXCLUDE_DIRECTIVE).append(":=\""); //$NON-NLS-1$
+ for (int i = 0; i < excludes.length; i++) {
+ if (i > 0)
+ result.append(',');
+ result.append(excludes[i]);
+ }
+ result.append("\""); //$NON-NLS-1$
+ }
+ if (includes != null) {
+ result.append(';').append(Constants.INCLUDE_DIRECTIVE).append(":=\""); //$NON-NLS-1$
+ for (int i = 0; i < includes.length; i++) {
+ if (i > 0)
+ result.append(',');
+ result.append(includes[i]);
+ }
+ result.append("\""); //$NON-NLS-1$
+ }
+ }
+ if (Constants.ECLIPSE_LAZYSTART.equals(keyString) || Constants.ECLIPSE_AUTOSTART.equals(keyString)) {
+ if (!storageHook.isAutoStartable())
+ return null;
+ if (storageHook.getLazyStartExcludes() == null)
+ return Boolean.TRUE.toString();
+ StringBuffer result = new StringBuffer(storageHook.isLazyStart() ? Boolean.TRUE.toString() : Boolean.FALSE.toString());
+ result.append(";").append(Constants.ECLIPSE_LAZYSTART_EXCEPTIONS).append("=\""); //$NON-NLS-1$ //$NON-NLS-2$
+ String[] exceptions = storageHook.getLazyStartExcludes();
+ for (int i = 0; i < exceptions.length; i++) {
+ if (i > 0)
+ result.append(","); //$NON-NLS-1$
+ result.append(exceptions[i]);
+ }
+ result.append("\""); //$NON-NLS-1$
+ return result.toString();
+ }
+ if (Constants.BUNDLE_MANIFESTVERSION.equals(keyString))
+ return storageHook.getBundleManifestVersion() == 0 ? null : Integer.toString(storageHook.getBundleManifestVersion());
+ if (SERVICE_COMPONENT.equals(keyString))
+ return storageHook.getServiceComponent();
+ Dictionary<String, String> result = getManifest();
+ if (DEBUG)
+ System.out.println("Manifest read because of header: " + key); //$NON-NLS-1$
+ return result == null ? null : result.get(key);
+ }
+
+ public String remove(Object key) {
+ return getManifest().remove(key);
+ }
+
+ public String put(String key, String value) {
+ return getManifest().put(key, value);
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/ClasspathManifest.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/ClasspathManifest.java
new file mode 100644
index 000000000..80e3c28cd
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/ClasspathManifest.java
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2009 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.core.runtime.internal.adaptor;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.jar.Manifest;
+import org.eclipse.osgi.baseadaptor.BaseData;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
+import org.eclipse.osgi.baseadaptor.loader.*;
+import org.eclipse.osgi.framework.util.KeyedElement;
+
+public class ClasspathManifest implements KeyedElement {
+ public static final Object KEY = new Object();
+ public static final int HASHCODE = KEY.hashCode();
+
+ private Manifest manifest;
+ private boolean initialized = false;
+
+ public int getKeyHashCode() {
+ return HASHCODE;
+ }
+
+ public boolean compare(KeyedElement other) {
+ return other.getKey() == KEY;
+ }
+
+ public Object getKey() {
+ return KEY;
+ }
+
+ public synchronized Manifest getManifest(ClasspathEntry cpEntry, ClasspathManager loader) {
+ if (initialized)
+ return manifest;
+ if (!hasPackageInfo(cpEntry, loader)) {
+ initialized = true;
+ manifest = null;
+ return manifest;
+ }
+ BundleEntry mfEntry = cpEntry.getBundleFile().getEntry(org.eclipse.osgi.framework.internal.core.Constants.OSGI_BUNDLE_MANIFEST);
+ if (mfEntry != null) {
+ InputStream manIn = null;
+ try {
+ try {
+ manIn = mfEntry.getInputStream();
+ manifest = new Manifest(manIn);
+ } finally {
+ if (manIn != null)
+ manIn.close();
+ }
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+ initialized = true;
+ return manifest;
+ }
+
+ private boolean hasPackageInfo(ClasspathEntry cpEntry, ClasspathManager loader) {
+ BaseData bundledata = null;
+ if (cpEntry.getBundleFile() == loader.getBaseData().getBundleFile())
+ bundledata = loader.getBaseData();
+ if (bundledata == null) {
+ FragmentClasspath[] fragCPs = loader.getFragmentClasspaths();
+ if (fragCPs != null)
+ for (int i = 0; i < fragCPs.length; i++)
+ if (cpEntry.getBundleFile() == fragCPs[i].getBundleData().getBundleFile()) {
+ bundledata = fragCPs[i].getBundleData();
+ break;
+ }
+ }
+ if (bundledata == null)
+ return true;
+ EclipseStorageHook storageHook = (EclipseStorageHook) bundledata.getStorageHook(EclipseStorageHook.KEY);
+ return storageHook == null ? true : storageHook.hasPackageInfo();
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/ContextFinder.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/ContextFinder.java
new file mode 100644
index 000000000..9ecc15b45
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/ContextFinder.java
@@ -0,0 +1,175 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.runtime.internal.adaptor;
+
+import java.io.IOException;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.*;
+import org.eclipse.osgi.framework.adaptor.BundleClassLoader;
+
+public class ContextFinder extends ClassLoader implements PrivilegedAction<List<ClassLoader>> {
+ static final class Finder extends SecurityManager {
+ public Class<?>[] getClassContext() {
+ return super.getClassContext();
+ }
+ }
+
+ //This is used to detect cycle that could be caused while delegating the loading to other classloaders
+ //It keeps track on a thread basis of the set of requested classes and resources
+ private static ThreadLocal<Set<String>> cycleDetector = new ThreadLocal<Set<String>>();
+ static ClassLoader finderClassLoader;
+ static Finder contextFinder;
+ static {
+ AccessController.doPrivileged(new PrivilegedAction<Object>() {
+ public Object run() {
+ finderClassLoader = ContextFinder.class.getClassLoader();
+ contextFinder = new Finder();
+ return null;
+ }
+ });
+ }
+
+ private static Class<ContextFinder> THIS = ContextFinder.class;
+
+ private final ClassLoader parentContextClassLoader;
+
+ public ContextFinder(ClassLoader contextClassLoader) {
+ super(contextClassLoader);
+ this.parentContextClassLoader = contextClassLoader != null ? contextClassLoader : new ClassLoader(Object.class.getClassLoader()) {/*boot classloader*/};
+ }
+
+ // Return a list of all classloaders on the stack that are neither the
+ // ContextFinder classloader nor the boot classloader. The last classloader
+ // in the list is either a bundle classloader or the framework's classloader
+ // We assume that the bootclassloader never uses the context classloader to find classes in itself.
+ List<ClassLoader> basicFindClassLoaders() {
+ Class<?>[] stack = contextFinder.getClassContext();
+ List<ClassLoader> result = new ArrayList<ClassLoader>(1);
+ ClassLoader previousLoader = null;
+ for (int i = 1; i < stack.length; i++) {
+ ClassLoader tmp = stack[i].getClassLoader();
+ if (stack[i] != THIS && tmp != null && tmp != this) {
+ if (checkClassLoader(tmp)) {
+ if (previousLoader != tmp) {
+ result.add(tmp);
+ previousLoader = tmp;
+ }
+ }
+ // stop at the framework classloader or the first bundle classloader
+ if (tmp == finderClassLoader || tmp instanceof BundleClassLoader)
+ break;
+ }
+ }
+ return result;
+ }
+
+ // ensures that a classloader does not have the ContextFinder as part of the
+ // parent hierachy. A classloader which has the ContextFinder as a parent must
+ // not be used as a delegate, otherwise we endup in endless recursion.
+ private boolean checkClassLoader(ClassLoader classloader) {
+ if (classloader == null || classloader == getParent())
+ return false;
+ for (ClassLoader parent = classloader.getParent(); parent != null; parent = parent.getParent())
+ if (parent == this)
+ return false;
+ return true;
+ }
+
+ private List<ClassLoader> findClassLoaders() {
+ if (System.getSecurityManager() == null)
+ return basicFindClassLoaders();
+ return AccessController.doPrivileged(this);
+ }
+
+ public List<ClassLoader> run() {
+ return basicFindClassLoaders();
+ }
+
+ //Return whether the request for loading "name" should proceed.
+ //False is returned when a cycle is being detected
+ private boolean startLoading(String name) {
+ Set<String> classesAndResources = cycleDetector.get();
+ if (classesAndResources != null && classesAndResources.contains(name))
+ return false;
+
+ if (classesAndResources == null) {
+ classesAndResources = new HashSet<String>(3);
+ cycleDetector.set(classesAndResources);
+ }
+ classesAndResources.add(name);
+ return true;
+ }
+
+ private void stopLoading(String name) {
+ cycleDetector.get().remove(name);
+ }
+
+ protected Class<?> loadClass(String arg0, boolean arg1) throws ClassNotFoundException {
+ //Shortcut cycle
+ if (startLoading(arg0) == false)
+ throw new ClassNotFoundException(arg0);
+
+ try {
+ List<ClassLoader> toConsult = findClassLoaders();
+ for (Iterator<ClassLoader> loaders = toConsult.iterator(); loaders.hasNext();)
+ try {
+ return loaders.next().loadClass(arg0);
+ } catch (ClassNotFoundException e) {
+ // go to the next class loader
+ }
+ // avoid calling super.loadClass here because it checks the local cache (bug 127963)
+ return parentContextClassLoader.loadClass(arg0);
+ } finally {
+ stopLoading(arg0);
+ }
+ }
+
+ public URL getResource(String arg0) {
+ //Shortcut cycle
+ if (startLoading(arg0) == false)
+ return null;
+ try {
+ List<ClassLoader> toConsult = findClassLoaders();
+ for (Iterator<ClassLoader> loaders = toConsult.iterator(); loaders.hasNext();) {
+ URL result = loaders.next().getResource(arg0);
+ if (result != null)
+ return result;
+ // go to the next class loader
+ }
+ return super.getResource(arg0);
+ } finally {
+ stopLoading(arg0);
+ }
+ }
+
+ protected Enumeration<URL> findResources(String arg0) throws IOException {
+ //Shortcut cycle
+ if (startLoading(arg0) == false) {
+ @SuppressWarnings("unchecked")
+ Enumeration<URL> result = Collections.enumeration(Collections.EMPTY_LIST);
+ return result;
+ }
+ try {
+ List<ClassLoader> toConsult = findClassLoaders();
+ for (Iterator<ClassLoader> loaders = toConsult.iterator(); loaders.hasNext();) {
+ Enumeration<URL> result = loaders.next().getResources(arg0);
+ if (result != null && result.hasMoreElements())
+ return result;
+ // go to the next class loader
+ }
+ return super.findResources(arg0);
+ } finally {
+ stopLoading(arg0);
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/DefaultStartupMonitor.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/DefaultStartupMonitor.java
new file mode 100644
index 000000000..9ac29d38d
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/DefaultStartupMonitor.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Andrew Niefer - IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.runtime.internal.adaptor;
+
+import java.lang.reflect.Method;
+import org.eclipse.core.runtime.adaptor.EclipseStarter;
+import org.eclipse.core.runtime.internal.stats.StatsManager;
+import org.eclipse.osgi.framework.internal.core.FrameworkProperties;
+import org.eclipse.osgi.service.runnable.StartupMonitor;
+
+public class DefaultStartupMonitor implements StartupMonitor {
+
+ private Method updateMethod = null;
+ private Runnable splashHandler = null;
+
+ /**
+ * Create a new startup monitor using the given splash handler. The splash handle must
+ * have an updateSplash method.
+ *
+ * @param splashHandler
+ * @throws IllegalStateException
+ */
+ public DefaultStartupMonitor(Runnable splashHandler) throws IllegalStateException {
+ this.splashHandler = splashHandler;
+
+ try {
+ updateMethod = splashHandler.getClass().getMethod("updateSplash", (Class[]) null); //$NON-NLS-1$
+ } catch (SecurityException e) {
+ throw (IllegalStateException) new IllegalStateException(e.getMessage()).initCause(e);
+ } catch (NoSuchMethodException e) {
+ //TODO maybe we could do something else in the update method in this case, like print something to the console?
+ throw (IllegalStateException) new IllegalStateException(e.getMessage()).initCause(e);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.core.runtime.adaptor.StartupMonitor#update()
+ */
+ public void update() {
+ if (updateMethod != null) {
+ try {
+ updateMethod.invoke(splashHandler, (Object[]) null);
+ } catch (Throwable e) {
+ // ignore, this is best effort
+ }
+ } else {
+ //TODO maybe we could print something interesting to the console?
+ }
+ }
+
+ public void applicationRunning() {
+ if (EclipseStarter.debug) {
+ String timeString = FrameworkProperties.getProperty("eclipse.startTime"); //$NON-NLS-1$
+ long time = timeString == null ? 0L : Long.parseLong(timeString);
+ System.out.println("Application Started: " + (System.currentTimeMillis() - time)); //$NON-NLS-1$
+ }
+ StatsManager.doneBooting();
+ splashHandler.run();
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseAdaptorHook.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseAdaptorHook.java
new file mode 100644
index 000000000..657e6be86
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseAdaptorHook.java
@@ -0,0 +1,231 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.core.runtime.internal.adaptor;
+
+import java.io.IOException;
+import java.net.URLConnection;
+import java.util.*;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.SAXParserFactory;
+import org.eclipse.core.runtime.adaptor.LocationManager;
+import org.eclipse.osgi.baseadaptor.*;
+import org.eclipse.osgi.baseadaptor.hooks.AdaptorHook;
+import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.framework.debug.FrameworkDebugOptions;
+import org.eclipse.osgi.framework.internal.core.BundleHost;
+import org.eclipse.osgi.framework.internal.core.FrameworkProperties;
+import org.eclipse.osgi.framework.log.FrameworkLog;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+import org.eclipse.osgi.internal.baseadaptor.AdaptorUtil;
+import org.eclipse.osgi.service.datalocation.Location;
+import org.eclipse.osgi.service.pluginconversion.PluginConverter;
+import org.eclipse.osgi.service.resolver.PlatformAdmin;
+import org.eclipse.osgi.service.urlconversion.URLConverter;
+import org.osgi.framework.*;
+
+public class EclipseAdaptorHook implements AdaptorHook, HookConfigurator {
+ /** The SAX factory name */
+ public static final String SAXFACTORYNAME = "javax.xml.parsers.SAXParserFactory"; //$NON-NLS-1$
+ /** The DOM factory name */
+ public static final String DOMFACTORYNAME = "javax.xml.parsers.DocumentBuilderFactory"; //$NON-NLS-1$
+ private static final String RUNTIME_ADAPTOR = FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME + "/eclipseadaptor"; //$NON-NLS-1$
+ private static final String OPTION_CONVERTER = RUNTIME_ADAPTOR + "/converter/debug"; //$NON-NLS-1$
+ private static final String OPTION_LOCATION = RUNTIME_ADAPTOR + "/debug/location"; //$NON-NLS-1$
+ private static final String OPTION_CACHEDMANIFEST = RUNTIME_ADAPTOR + "/debug/cachedmanifest"; //$NON-NLS-1$
+ static final boolean SET_TCCL_XMLFACTORY = "true".equals(FrameworkProperties.getProperty("eclipse.parsers.setTCCL", "true"));//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+
+ private BaseAdaptor adaptor;
+ private boolean noXML = false;
+ private List<ServiceRegistration<?>> registrations = new ArrayList<ServiceRegistration<?>>(10);
+
+ /**
+ * @throws BundleException
+ */
+ public void frameworkStart(BundleContext context) throws BundleException {
+ registrations.clear();
+ registerEndorsedXMLParser(context);
+ Dictionary<String, Object> locationProperties = new Hashtable<String, Object>(1);
+ Location location = LocationManager.getUserLocation();
+ if (location != null) {
+ locationProperties.put("type", LocationManager.PROP_USER_AREA); //$NON-NLS-1$
+ registrations.add(context.registerService(Location.class.getName(), location, locationProperties));
+ }
+ location = LocationManager.getInstanceLocation();
+ if (location != null) {
+ locationProperties.put("type", LocationManager.PROP_INSTANCE_AREA); //$NON-NLS-1$
+ registrations.add(context.registerService(Location.class.getName(), location, locationProperties));
+ }
+ location = LocationManager.getConfigurationLocation();
+ if (location != null) {
+ locationProperties.put("type", LocationManager.PROP_CONFIG_AREA); //$NON-NLS-1$
+ registrations.add(context.registerService(Location.class.getName(), location, locationProperties));
+ }
+ location = LocationManager.getInstallLocation();
+ if (location != null) {
+ locationProperties.put("type", LocationManager.PROP_INSTALL_AREA); //$NON-NLS-1$
+ registrations.add(context.registerService(Location.class.getName(), location, locationProperties));
+ }
+
+ location = LocationManager.getEclipseHomeLocation();
+ if (location != null) {
+ locationProperties.put("type", LocationManager.PROP_HOME_LOCATION_AREA); //$NON-NLS-1$
+ registrations.add(context.registerService(Location.class.getName(), location, locationProperties));
+ }
+
+ Dictionary<String, Object> urlProperties = new Hashtable<String, Object>();
+ urlProperties.put("protocol", new String[] {"bundleentry", "bundleresource"}); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ registrations.add(context.registerService(URLConverter.class.getName(), new URLConverterImpl(), urlProperties));
+
+ registrations.add(AdaptorUtil.register(org.eclipse.osgi.service.environment.EnvironmentInfo.class.getName(), EclipseEnvironmentInfo.getDefault(), context));
+ registrations.add(AdaptorUtil.register(PlatformAdmin.class.getName(), adaptor.getPlatformAdmin(), context));
+ PluginConverter converter = PluginConverterImpl.getDefault();
+ if (converter == null)
+ converter = new PluginConverterImpl(adaptor, context);
+ registrations.add(AdaptorUtil.register(PluginConverter.class.getName(), converter, context));
+ registrations.add(AdaptorUtil.register(org.eclipse.osgi.service.localization.BundleLocalization.class.getName(), new BundleLocalizationImpl(), context));
+ }
+
+ private void registerEndorsedXMLParser(BundleContext bc) {
+ try {
+ Class.forName(SAXFACTORYNAME);
+ registrations.add(bc.registerService(SAXFACTORYNAME, new ParsingService(true), null));
+ Class.forName(DOMFACTORYNAME);
+ registrations.add(bc.registerService(DOMFACTORYNAME, new ParsingService(false), null));
+ } catch (ClassNotFoundException e) {
+ noXML = true;
+ if (Debug.DEBUG_ENABLED) {
+ String message = EclipseAdaptorMsg.ECLIPSE_ADAPTOR_ERROR_XML_SERVICE;
+ adaptor.getFrameworkLog().log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, message, 0, e, null));
+ }
+ }
+ }
+
+ private static class ParsingService implements ServiceFactory<Object> {
+ private final boolean isSax;
+
+ public ParsingService(boolean isSax) {
+ this.isSax = isSax;
+ }
+
+ public Object getService(Bundle bundle, ServiceRegistration<Object> registration) {
+ BundleHost host = (bundle instanceof BundleHost) ? (BundleHost) bundle : null;
+ if (!SET_TCCL_XMLFACTORY || bundle == null)
+ return createService();
+ /*
+ * Set the TCCL while creating jaxp factory instances to the
+ * requesting bundles class loader. This is needed to
+ * work around bug 285505. There are issues if multiple
+ * xerces implementations are available on the bundles class path
+ *
+ * The real issue is that the ContextFinder will only delegate
+ * to the framework class loader in this case. This class
+ * loader forces the requesting bundle to be delegated to for
+ * TCCL loads.
+ */
+ final ClassLoader savedClassLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ ClassLoader cl = host.getClassLoader();
+ if (cl != null)
+ Thread.currentThread().setContextClassLoader(cl);
+ return createService();
+ } finally {
+ Thread.currentThread().setContextClassLoader(savedClassLoader);
+ }
+ }
+
+ private Object createService() {
+ if (isSax)
+ return SAXParserFactory.newInstance();
+ return DocumentBuilderFactory.newInstance();
+ }
+
+ public void ungetService(Bundle bundle, ServiceRegistration<Object> registration, Object service) {
+ // Do nothing.
+ }
+ }
+
+ /**
+ * @throws BundleException
+ */
+ public void frameworkStop(BundleContext context) throws BundleException {
+ printStats();
+ if (!noXML)
+ PluginParser.releaseXMLParsing();
+ // unregister services
+ for (ServiceRegistration<?> registration : registrations)
+ registration.unregister();
+ registrations.clear();
+ }
+
+ private void printStats() {
+ FrameworkDebugOptions debugOptions = FrameworkDebugOptions.getDefault();
+ if (debugOptions == null)
+ return;
+ String registryParsing = debugOptions.getOption("org.eclipse.core.runtime/registry/parsing/timing/value"); //$NON-NLS-1$
+ if (registryParsing != null)
+ MessageHelper.debug("Time spent in registry parsing: " + registryParsing); //$NON-NLS-1$
+ String packageAdminResolution = debugOptions.getOption("debug.packageadmin/timing/value"); //$NON-NLS-1$
+ if (packageAdminResolution != null)
+ System.out.println("Time spent in package admin resolve: " + packageAdminResolution); //$NON-NLS-1$
+ String constraintResolution = debugOptions.getOption("org.eclipse.core.runtime.adaptor/resolver/timing/value"); //$NON-NLS-1$
+ if (constraintResolution != null)
+ System.out.println("Time spent resolving the dependency system: " + constraintResolution); //$NON-NLS-1$
+ }
+
+ public void frameworkStopping(BundleContext context) {
+ // do nothing
+ }
+
+ public void addProperties(Properties properties) {
+ // do nothing
+ }
+
+ /**
+ * @throws IOException
+ */
+ public URLConnection mapLocationToURLConnection(String location) throws IOException {
+ // do nothing
+ return null;
+ }
+
+ public void handleRuntimeError(Throwable error) {
+ // do nothing
+ }
+
+ public FrameworkLog createFrameworkLog() {
+ // do nothing
+ return null;
+ }
+
+ public void initialize(BaseAdaptor initAdaptor) {
+ this.adaptor = initAdaptor;
+ // EnvironmentInfo has to be initialized first to compute defaults for system context (see bug 88925)
+ EclipseEnvironmentInfo.getDefault();
+ setDebugOptions();
+ }
+
+ private void setDebugOptions() {
+ FrameworkDebugOptions options = FrameworkDebugOptions.getDefault();
+ // may be null if debugging is not enabled
+ if (options == null)
+ return;
+ PluginConverterImpl.DEBUG = options.getBooleanOption(OPTION_CONVERTER, false);
+ BasicLocation.DEBUG = options.getBooleanOption(OPTION_LOCATION, false);
+ CachedManifest.DEBUG = options.getBooleanOption(OPTION_CACHEDMANIFEST, false);
+ }
+
+ public void addHooks(HookRegistry hookRegistry) {
+ hookRegistry.addAdaptorHook(this);
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseAppLauncher.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseAppLauncher.java
new file mode 100644
index 000000000..a4106fdb6
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseAppLauncher.java
@@ -0,0 +1,159 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.core.runtime.internal.adaptor;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+import org.eclipse.core.runtime.adaptor.EclipseStarter;
+import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor;
+import org.eclipse.osgi.framework.internal.core.Constants;
+import org.eclipse.osgi.framework.internal.core.FrameworkProperties;
+import org.eclipse.osgi.framework.log.FrameworkLog;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+import org.eclipse.osgi.internal.profile.Profile;
+import org.eclipse.osgi.service.runnable.*;
+import org.osgi.framework.*;
+
+public class EclipseAppLauncher implements ApplicationLauncher {
+ volatile private ParameterizedRunnable runnable = null;
+ private Object appContext = null;
+ private Semaphore runningLock = new Semaphore(1);
+ private Semaphore waitForAppLock = new Semaphore(0);
+ private BundleContext context;
+ private boolean relaunch = false;
+ private boolean failOnNoDefault = false;
+ private FrameworkLog log;
+
+ public EclipseAppLauncher(BundleContext context, boolean relaunch, boolean failOnNoDefault, FrameworkLog log) {
+ this.context = context;
+ this.relaunch = relaunch;
+ this.failOnNoDefault = failOnNoDefault;
+ this.log = log;
+ findRunnableService();
+ }
+
+ /*
+ * Used for backwards compatibility with < 3.2 runtime
+ */
+ private void findRunnableService() {
+ // look for a ParameterizedRunnable registered as a service by runtimes (3.0, 3.1)
+ String appClass = ParameterizedRunnable.class.getName();
+ ServiceReference<?>[] runRefs = null;
+ try {
+ runRefs = context.getServiceReferences(ParameterizedRunnable.class.getName(), "(&(objectClass=" + appClass + ")(eclipse.application=*))"); //$NON-NLS-1$//$NON-NLS-2$
+ } catch (InvalidSyntaxException e) {
+ // ignore this. It should never happen as we have tested the above format.
+ }
+ if (runRefs != null && runRefs.length > 0) {
+ // found the service use it as the application.
+ runnable = (ParameterizedRunnable) context.getService(runRefs[0]);
+ // we will never be able to relaunch with a pre 3.2 runtime
+ relaunch = false;
+ waitForAppLock.release();
+ }
+ }
+
+ /*
+ * Starts this application launcher on the current thread. This method
+ * should be called by the main thread to ensure that applications are
+ * launched in the main thread.
+ */
+ public Object start(Object defaultContext) throws Exception {
+ // here we assume that launch has been called by runtime before we started
+ // TODO this may be a bad assumption but it works for now because we register the app launcher as a service and runtime synchronously calls launch on the service
+ if (failOnNoDefault && runnable == null)
+ throw new IllegalStateException(EclipseAdaptorMsg.ECLIPSE_STARTUP_ERROR_NO_APPLICATION);
+ Object result = null;
+ boolean doRelaunch;
+ do {
+ try {
+ result = runApplication(defaultContext);
+ } catch (Exception e) {
+ if (!relaunch || (context.getBundle().getState() & Bundle.ACTIVE) == 0)
+ throw e;
+ if (log != null)
+ log.log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, EclipseAdaptorMsg.ECLIPSE_STARTUP_APP_ERROR, 1, e, null));
+ }
+ doRelaunch = (relaunch && (context.getBundle().getState() & Bundle.ACTIVE) != 0) || FrameworkProperties.getProperty(Constants.PROP_OSGI_RELAUNCH) != null;
+ } while (doRelaunch);
+ return result;
+ }
+
+ /*
+ * Waits for an application to be launched and the runs the application on the
+ * current thread (main).
+ */
+ private Object runApplication(Object defaultContext) throws Exception {
+ // wait for an application to be launched.
+ waitForAppLock.acquire();
+ // an application is ready; acquire the running lock.
+ // this must happen after we have acquired an application (by acquiring waitForAppLock above).
+ runningLock.acquire();
+ if (EclipseStarter.debug) {
+ String timeString = FrameworkProperties.getProperty("eclipse.startTime"); //$NON-NLS-1$
+ long time = timeString == null ? 0L : Long.parseLong(timeString);
+ System.out.println("Starting application: " + (System.currentTimeMillis() - time)); //$NON-NLS-1$
+ }
+ if (Profile.PROFILE && (Profile.STARTUP || Profile.BENCHMARK))
+ Profile.logTime("EclipseStarter.run(Object)()", "framework initialized! starting application..."); //$NON-NLS-1$ //$NON-NLS-2$
+ try {
+ // run the actual application on the current thread (main).
+ return runnable.run(appContext != null ? appContext : defaultContext);
+ } finally {
+ if (Profile.PROFILE && Profile.STARTUP)
+ Profile.logExit("EclipseStarter.run(Object)()"); //$NON-NLS-1$
+ // free the runnable application and release the lock to allow another app to be launched.
+ runnable = null;
+ appContext = null;
+ runningLock.release();
+ }
+ }
+
+ public void launch(ParameterizedRunnable app, Object applicationContext) {
+ waitForAppLock.acquire(-1); // clear out any pending apps notifications
+ if (!runningLock.acquire(-1)) // check to see if an application is currently running
+ throw new IllegalStateException("An application is aready running."); //$NON-NLS-1$
+ this.runnable = app;
+ this.appContext = applicationContext;
+ waitForAppLock.release(); // notify the main thread to launch an application.
+ runningLock.release(); // release the running lock
+ }
+
+ public void shutdown() {
+ // this method will aquire and keep the runningLock to prevent
+ // all future application launches.
+ if (runningLock.acquire(-1))
+ return; // no application is currently running.
+ ParameterizedRunnable currentRunnable = runnable;
+ if (currentRunnable instanceof ApplicationRunnable) {
+ ((ApplicationRunnable) currentRunnable).stop();
+ runningLock.acquire(60000); // timeout after 1 minute.
+ }
+ }
+
+ /*
+ * Similar to the start method this method will restart the default method on current thread.
+ * This method assumes that the default application was launched at least once and that an ApplicationDescriptor
+ * exists that can be used to relaunch the default application.
+ */
+ public Object reStart(Object argument) throws Exception {
+ ServiceReference<?> ref[] = null;
+ ref = context.getServiceReferences("org.osgi.service.application.ApplicationDescriptor", "(eclipse.application.default=true)"); //$NON-NLS-1$//$NON-NLS-2$
+ if (ref != null && ref.length > 0) {
+ Object defaultApp = context.getService(ref[0]);
+ Method launch = defaultApp.getClass().getMethod("launch", new Class[] {Map.class}); //$NON-NLS-1$
+ launch.invoke(defaultApp, new Object[] {null});
+ return start(argument);
+ }
+ throw new IllegalStateException(EclipseAdaptorMsg.ECLIPSE_STARTUP_ERROR_NO_APPLICATION);
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseClassLoadingHook.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseClassLoadingHook.java
new file mode 100644
index 000000000..c24307844
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseClassLoadingHook.java
@@ -0,0 +1,243 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.core.runtime.internal.adaptor;
+
+import java.io.File;
+import java.security.ProtectionDomain;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+import org.eclipse.osgi.baseadaptor.*;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile;
+import org.eclipse.osgi.baseadaptor.hooks.ClassLoadingHook;
+import org.eclipse.osgi.baseadaptor.loader.*;
+import org.eclipse.osgi.framework.adaptor.BundleProtectionDomain;
+import org.eclipse.osgi.framework.adaptor.ClassLoaderDelegate;
+import org.eclipse.osgi.framework.internal.core.FrameworkProperties;
+import org.eclipse.osgi.internal.baseadaptor.BaseClassLoadingHook;
+import org.eclipse.osgi.internal.baseadaptor.BaseStorageHook;
+
+public class EclipseClassLoadingHook implements ClassLoadingHook, HookConfigurator {
+ private static String[] NL_JAR_VARIANTS = buildNLJarVariants(EclipseEnvironmentInfo.getDefault().getNL());
+ private static boolean DEFINE_PACKAGES;
+ private final static boolean DEFINE_PACKAGE_ATTRIBUTES = !"noattributes".equals(FrameworkProperties.getProperty("osgi.classloader.define.packages")); //$NON-NLS-1$ //$NON-NLS-2$
+ private static String[] LIB_VARIANTS = buildLibraryVariants();
+ private Object pkgLock = new Object();
+
+ static {
+ try {
+ Class.forName("java.lang.Package"); //$NON-NLS-1$
+ DEFINE_PACKAGES = true;
+ } catch (ClassNotFoundException e) {
+ DEFINE_PACKAGES = false;
+ }
+ }
+
+ private static String[] buildLibraryVariants() {
+ List<String> result = new ArrayList<String>();
+ EclipseEnvironmentInfo info = EclipseEnvironmentInfo.getDefault();
+ result.add("ws/" + info.getWS() + "/"); //$NON-NLS-1$ //$NON-NLS-2$
+ result.add("os/" + info.getOS() + "/" + info.getOSArch() + "/"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ result.add("os/" + info.getOS() + "/"); //$NON-NLS-1$ //$NON-NLS-2$
+ String nl = info.getNL();
+ nl = nl.replace('_', '/');
+ while (nl.length() > 0) {
+ result.add("nl/" + nl + "/"); //$NON-NLS-1$ //$NON-NLS-2$
+ int i = nl.lastIndexOf('/');
+ nl = (i < 0) ? "" : nl.substring(0, i); //$NON-NLS-1$
+ }
+ result.add(""); //$NON-NLS-1$
+ return result.toArray(new String[result.size()]);
+ }
+
+ public byte[] processClass(String name, byte[] classbytes, ClasspathEntry classpathEntry, BundleEntry entry, ClasspathManager manager) {
+ if (!DEFINE_PACKAGES)
+ return null;
+ // Define the package if it is not the default package.
+ int lastIndex = name.lastIndexOf('.');
+ if (lastIndex < 0)
+ return null;
+ String packageName = name.substring(0, lastIndex);
+ Object pkg;
+ synchronized (pkgLock) {
+ pkg = manager.getBaseClassLoader().publicGetPackage(packageName);
+ if (pkg != null)
+ return null;
+ }
+
+ // get info about the package from the classpath entry's manifest.
+ String specTitle = null, specVersion = null, specVendor = null, implTitle = null, implVersion = null, implVendor = null;
+
+ if (DEFINE_PACKAGE_ATTRIBUTES) {
+ ClasspathManifest cpm = (ClasspathManifest) classpathEntry.getUserObject(ClasspathManifest.KEY);
+ if (cpm == null) {
+ cpm = new ClasspathManifest();
+ classpathEntry.addUserObject(cpm);
+ }
+ Manifest mf = cpm.getManifest(classpathEntry, manager);
+ if (mf != null) {
+ Attributes mainAttributes = mf.getMainAttributes();
+ String dirName = packageName.replace('.', '/') + '/';
+ Attributes packageAttributes = mf.getAttributes(dirName);
+ boolean noEntry = false;
+ if (packageAttributes == null) {
+ noEntry = true;
+ packageAttributes = mainAttributes;
+ }
+ specTitle = packageAttributes.getValue(Attributes.Name.SPECIFICATION_TITLE);
+ if (specTitle == null && !noEntry)
+ specTitle = mainAttributes.getValue(Attributes.Name.SPECIFICATION_TITLE);
+ specVersion = packageAttributes.getValue(Attributes.Name.SPECIFICATION_VERSION);
+ if (specVersion == null && !noEntry)
+ specVersion = mainAttributes.getValue(Attributes.Name.SPECIFICATION_VERSION);
+ specVendor = packageAttributes.getValue(Attributes.Name.SPECIFICATION_VENDOR);
+ if (specVendor == null && !noEntry)
+ specVendor = mainAttributes.getValue(Attributes.Name.SPECIFICATION_VENDOR);
+ implTitle = packageAttributes.getValue(Attributes.Name.IMPLEMENTATION_TITLE);
+ if (implTitle == null && !noEntry)
+ implTitle = mainAttributes.getValue(Attributes.Name.IMPLEMENTATION_TITLE);
+ implVersion = packageAttributes.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
+ if (implVersion == null && !noEntry)
+ implVersion = mainAttributes.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
+ implVendor = packageAttributes.getValue(Attributes.Name.IMPLEMENTATION_VENDOR);
+ if (implVendor == null && !noEntry)
+ implVendor = mainAttributes.getValue(Attributes.Name.IMPLEMENTATION_VENDOR);
+ }
+ }
+
+ // The package is not defined yet define it before we define the class.
+ // TODO still need to seal packages.
+ synchronized (pkgLock) {
+ pkg = manager.getBaseClassLoader().publicGetPackage(packageName);
+ if (pkg != null)
+ return null;
+ manager.getBaseClassLoader().publicDefinePackage(packageName, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor, null);
+ }
+ // not doing any byte processing
+ return null;
+ }
+
+ public boolean addClassPathEntry(ArrayList<ClasspathEntry> cpEntries, String cp, ClasspathManager hostmanager, BaseData sourcedata, ProtectionDomain sourcedomain) {
+ String var = hasPrefix(cp);
+ if (var != null)
+ // find internal library using eclipse predefined vars
+ return addInternalClassPath(var, cpEntries, cp, hostmanager, sourcedata, sourcedomain);
+ if (cp.startsWith(BaseStorageHook.EXTERNAL_LIB_PREFIX)) {
+ cp = cp.substring(BaseStorageHook.EXTERNAL_LIB_PREFIX.length());
+ // find external library using system property substitution
+ ClasspathEntry cpEntry = hostmanager.getExternalClassPath(BaseStorageHook.substituteVars(cp), sourcedata, sourcedomain);
+ if (cpEntry != null) {
+ cpEntries.add(cpEntry);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean addInternalClassPath(String var, ArrayList<ClasspathEntry> cpEntries, String cp, ClasspathManager hostloader, BaseData sourcedata, ProtectionDomain sourcedomain) {
+ if (var.equals("ws")) //$NON-NLS-1$
+ return ClasspathManager.addClassPathEntry(cpEntries, "ws/" + EclipseEnvironmentInfo.getDefault().getWS() + cp.substring(4), hostloader, sourcedata, sourcedomain); //$NON-NLS-1$
+ if (var.equals("os")) //$NON-NLS-1$
+ return ClasspathManager.addClassPathEntry(cpEntries, "os/" + EclipseEnvironmentInfo.getDefault().getOS() + cp.substring(4), hostloader, sourcedata, sourcedomain); //$NON-NLS-1$
+ if (var.equals("nl")) { //$NON-NLS-1$
+ cp = cp.substring(4);
+ for (int i = 0; i < NL_JAR_VARIANTS.length; i++)
+ if (ClasspathManager.addClassPathEntry(cpEntries, "nl/" + NL_JAR_VARIANTS[i] + cp, hostloader, sourcedata, sourcedomain)) //$NON-NLS-1$
+ return true;
+ }
+ return false;
+ }
+
+ //return a String representing the string found between the $s
+ private static String hasPrefix(String libPath) {
+ if (libPath.startsWith("$ws$")) //$NON-NLS-1$
+ return "ws"; //$NON-NLS-1$
+ if (libPath.startsWith("$os$")) //$NON-NLS-1$
+ return "os"; //$NON-NLS-1$
+ if (libPath.startsWith("$nl$")) //$NON-NLS-1$
+ return "nl"; //$NON-NLS-1$
+ return null;
+ }
+
+ private static String[] buildNLJarVariants(String nl) {
+ List<String> result = new ArrayList<String>();
+ nl = nl.replace('_', '/');
+ while (nl.length() > 0) {
+ result.add("nl/" + nl + "/"); //$NON-NLS-1$ //$NON-NLS-2$
+ int i = nl.lastIndexOf('/');
+ nl = (i < 0) ? "" : nl.substring(0, i); //$NON-NLS-1$
+ }
+ result.add(""); //$NON-NLS-1$
+ return result.toArray(new String[result.size()]);
+ }
+
+ public void recordClassDefine(String name, Class<?> clazz, byte[] classbytes, ClasspathEntry classpathEntry, BundleEntry entry, ClasspathManager manager) {
+ // do nothing
+ }
+
+ public String findLibrary(BaseData data, String libName) {
+ if (libName.length() == 0)
+ return null;
+ if (libName.charAt(0) == '/' || libName.charAt(0) == '\\')
+ libName = libName.substring(1);
+ String mappedLibName = System.mapLibraryName(libName);
+ String result = searchVariants(data, mappedLibName);
+ if (result != null)
+ return result;
+ String[] mappedLibNames = BaseClassLoadingHook.mapLibraryNames(mappedLibName);
+ for (int i = 0; i < mappedLibNames.length && result == null; i++)
+ result = searchVariants(data, mappedLibNames[i]);
+ return result;
+ }
+
+ private String searchVariants(BaseData bundledata, String path) {
+ for (int i = 0; i < LIB_VARIANTS.length; i++) {
+ BundleFile baseBundleFile = bundledata.getBundleFile();
+ BundleEntry libEntry = baseBundleFile.getEntry(LIB_VARIANTS[i] + path);
+ if (libEntry != null) {
+ File libFile = baseBundleFile.getFile(LIB_VARIANTS[i] + path, true);
+ if (libFile == null)
+ return null;
+ // see bug 88697 - HP requires libraries to have executable permissions
+ if (org.eclipse.osgi.service.environment.Constants.OS_HPUX.equals(EclipseEnvironmentInfo.getDefault().getOS())) {
+ try {
+ // use the string array method in case there is a space in the path
+ Runtime.getRuntime().exec(new String[] {"chmod", "755", libFile.getAbsolutePath()}).waitFor(); //$NON-NLS-1$ //$NON-NLS-2$
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ return libFile.getAbsolutePath();
+ }
+ }
+ return null;
+ }
+
+ public ClassLoader getBundleClassLoaderParent() {
+ return null; // do nothing
+ }
+
+ public void addHooks(HookRegistry hookRegistry) {
+ hookRegistry.addClassLoadingHook(this);
+ }
+
+ public BaseClassLoader createClassLoader(ClassLoader parent, ClassLoaderDelegate delegate, BundleProtectionDomain domain, BaseData data, String[] bundleclasspath) {
+ // do nothing
+ return null;
+ }
+
+ public void initializedClassLoader(BaseClassLoader baseClassLoader, BaseData data) {
+ // do nothing
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseEnvironmentInfo.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseEnvironmentInfo.java
new file mode 100644
index 000000000..0d091d7f4
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseEnvironmentInfo.java
@@ -0,0 +1,243 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.runtime.internal.adaptor;
+
+import java.util.*;
+import org.eclipse.osgi.framework.internal.core.FrameworkProperties;
+import org.eclipse.osgi.service.environment.Constants;
+import org.eclipse.osgi.service.environment.EnvironmentInfo;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Internal class.
+ */
+public class EclipseEnvironmentInfo implements EnvironmentInfo {
+ private static EclipseEnvironmentInfo singleton;
+ private static String nl;
+ private static String os;
+ private static String ws;
+ private static String arch;
+ private volatile static String[] allArgs;
+ private volatile static String[] frameworkArgs;
+ private volatile static String[] appArgs;
+
+ // While we recognize the SunOS operating system, we change
+ // this internally to be Solaris.
+ private static final String INTERNAL_OS_SUNOS = "SunOS"; //$NON-NLS-1$
+ private static final String INTERNAL_OS_LINUX = "Linux"; //$NON-NLS-1$
+ private static final String INTERNAL_OS_MACOSX = "Mac OS"; //$NON-NLS-1$
+ private static final String INTERNAL_OS_AIX = "AIX"; //$NON-NLS-1$
+ private static final String INTERNAL_OS_HPUX = "HP-UX"; //$NON-NLS-1$
+ private static final String INTERNAL_OS_QNX = "QNX"; //$NON-NLS-1$
+ private static final String INTERNAL_OS_OS400 = "OS/400"; //$NON-NLS-1$
+ private static final String INTERNAL_OS_OS390 = "OS/390"; //$NON-NLS-1$
+ private static final String INTERNAL_OS_ZOS = "z/OS"; //$NON-NLS-1$
+
+ // While we recognize the i386 architecture, we change
+ // this internally to be x86.
+ private static final String INTERNAL_ARCH_I386 = "i386"; //$NON-NLS-1$
+ // While we recognize the amd64 architecture, we change
+ // this internally to be x86_64.
+ private static final String INTERNAL_AMD64 = "amd64"; //$NON-NLS-1$
+
+ private EclipseEnvironmentInfo() {
+ super();
+ setupSystemContext();
+ }
+
+ public static EclipseEnvironmentInfo getDefault() {
+ if (singleton == null)
+ singleton = new EclipseEnvironmentInfo();
+ return singleton;
+ }
+
+ public boolean inDevelopmentMode() {
+ return FrameworkProperties.getProperty("osgi.dev") != null; //$NON-NLS-1$
+ }
+
+ public boolean inDebugMode() {
+ return FrameworkProperties.getProperty("osgi.debug") != null; //$NON-NLS-1$
+ }
+
+ public String[] getCommandLineArgs() {
+ return allArgs;
+ }
+
+ public String[] getFrameworkArgs() {
+ return frameworkArgs;
+ }
+
+ public String[] getNonFrameworkArgs() {
+ return appArgs;
+ }
+
+ public String getOSArch() {
+ return arch;
+ }
+
+ public String getNL() {
+ return nl;
+ }
+
+ public String getOS() {
+ return os;
+ }
+
+ public String getWS() {
+ return ws;
+ }
+
+ /**
+ * Initializes the execution context for this run of the platform. The context
+ * includes information about the locale, operating system and window system.
+ *
+ * NOTE: The OS, WS, and ARCH values should never be null. The executable should
+ * be setting these values and therefore this code path is obsolete for Eclipse
+ * when run from the executable.
+ */
+ private static void setupSystemContext() {
+ // if the user didn't set the locale with a command line argument then use the default.
+ nl = FrameworkProperties.getProperty("osgi.nl"); //$NON-NLS-1$
+ if (nl != null) {
+ StringTokenizer tokenizer = new StringTokenizer(nl, "_"); //$NON-NLS-1$
+ int segments = tokenizer.countTokens();
+ try {
+ Locale userLocale = null;
+ switch (segments) {
+ case 1 :
+ // use the 2 arg constructor to maintain compatibility with 1.3.1
+ userLocale = new Locale(tokenizer.nextToken(), ""); //$NON-NLS-1$
+ break;
+ case 2 :
+ userLocale = new Locale(tokenizer.nextToken(), tokenizer.nextToken());
+ break;
+ case 3 :
+ userLocale = new Locale(tokenizer.nextToken(), tokenizer.nextToken(), tokenizer.nextToken());
+ break;
+ default :
+ // if the user passed us in a bogus value then log a message and use the default
+ System.err.println(NLS.bind(EclipseAdaptorMsg.error_badNL, nl));
+ userLocale = Locale.getDefault();
+ break;
+ }
+ Locale.setDefault(userLocale);
+ // TODO what the heck is this for?? why not just use osgi.nl
+ FrameworkProperties.setProperty("osgi.nl.user", nl); //$NON-NLS-1$
+ } catch (NoSuchElementException e) {
+ // fall through and use the default
+ }
+ }
+ nl = Locale.getDefault().toString();
+ FrameworkProperties.setProperty("osgi.nl", nl); //$NON-NLS-1$
+
+ // if the user didn't set the operating system with a command line
+ // argument then use the default.
+ os = FrameworkProperties.getProperty("osgi.os"); //$NON-NLS-1$
+ if (os == null) {
+ os = guessOS(FrameworkProperties.getProperty("os.name"));//$NON-NLS-1$);
+ FrameworkProperties.setProperty("osgi.os", os); //$NON-NLS-1$
+ }
+
+ // if the user didn't set the window system with a command line
+ // argument then use the default.
+ ws = FrameworkProperties.getProperty("osgi.ws"); //$NON-NLS-1$
+ if (ws == null) {
+ ws = guessWS(os);
+ FrameworkProperties.setProperty("osgi.ws", ws); //$NON-NLS-1$
+ }
+
+ // if the user didn't set the system architecture with a command line
+ // argument then use the default.
+ arch = FrameworkProperties.getProperty("osgi.arch"); //$NON-NLS-1$
+ if (arch == null) {
+ String name = FrameworkProperties.getProperty("os.arch");//$NON-NLS-1$
+ // Map i386 architecture to x86
+ if (name.equalsIgnoreCase(INTERNAL_ARCH_I386))
+ arch = Constants.ARCH_X86;
+ // Map amd64 architecture to x86_64
+ else if (name.equalsIgnoreCase(INTERNAL_AMD64))
+ arch = Constants.ARCH_X86_64;
+ else
+ arch = name;
+ FrameworkProperties.setProperty("osgi.arch", arch); //$NON-NLS-1$
+ }
+ }
+
+ public static void setAllArgs(String[] allArgs) {
+ // do not check if this is set already to allow arguments to change when multiple applications are launched
+ EclipseEnvironmentInfo.allArgs = allArgs;
+ }
+
+ public static void setAppArgs(String[] appArgs) {
+ // do not check if this is set already to allow arguments to change when multiple applications are launched
+ EclipseEnvironmentInfo.appArgs = appArgs;
+ }
+
+ public static void setFrameworkArgs(String[] frameworkArgs) {
+ // do not check if this is set already to allow arguments to change when multiple applications are launched
+ EclipseEnvironmentInfo.frameworkArgs = frameworkArgs;
+ }
+
+ public static String guessWS(String osName) {
+ // setup default values for known OSes if nothing was specified
+ if (osName.equals(Constants.OS_WIN32))
+ return Constants.WS_WIN32;
+ if (osName.equals(Constants.OS_LINUX))
+ return Constants.WS_GTK;
+ if (osName.equals(Constants.OS_MACOSX))
+ return Constants.WS_COCOA;
+ if (osName.equals(Constants.OS_HPUX))
+ return Constants.WS_MOTIF;
+ if (osName.equals(Constants.OS_AIX))
+ return Constants.WS_MOTIF;
+ if (osName.equals(Constants.OS_SOLARIS))
+ return Constants.WS_GTK;
+ if (osName.equals(Constants.OS_QNX))
+ return Constants.WS_PHOTON;
+ return Constants.WS_UNKNOWN;
+ }
+
+ public static String guessOS(String osName) {
+ // check to see if the OS name is "Windows 98" or some other
+ // flavour which should be converted to win32.
+ if (osName.regionMatches(true, 0, Constants.OS_WIN32, 0, 3))
+ return Constants.OS_WIN32;
+ // EXCEPTION: All mappings of SunOS convert to Solaris
+ if (osName.equalsIgnoreCase(INTERNAL_OS_SUNOS))
+ return Constants.OS_SOLARIS;
+ if (osName.equalsIgnoreCase(INTERNAL_OS_LINUX))
+ return Constants.OS_LINUX;
+ if (osName.equalsIgnoreCase(INTERNAL_OS_QNX))
+ return Constants.OS_QNX;
+ if (osName.equalsIgnoreCase(INTERNAL_OS_AIX))
+ return Constants.OS_AIX;
+ if (osName.equalsIgnoreCase(INTERNAL_OS_HPUX))
+ return Constants.OS_HPUX;
+ if (osName.equalsIgnoreCase(INTERNAL_OS_OS400))
+ return Constants.OS_OS400;
+ if (osName.equalsIgnoreCase(INTERNAL_OS_OS390))
+ return Constants.OS_OS390;
+ if (osName.equalsIgnoreCase(INTERNAL_OS_ZOS))
+ return Constants.OS_ZOS;
+ // os.name on Mac OS can be either Mac OS or Mac OS X
+ if (osName.regionMatches(true, 0, INTERNAL_OS_MACOSX, 0, INTERNAL_OS_MACOSX.length()))
+ return Constants.OS_MACOSX;
+ return Constants.OS_UNKNOWN;
+ }
+
+ public String getProperty(String key) {
+ return FrameworkProperties.getProperty(key);
+ }
+
+ public String setProperty(String key, String value) {
+ return FrameworkProperties.setProperty(key, value);
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseErrorHandler.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseErrorHandler.java
new file mode 100644
index 000000000..afad88113
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseErrorHandler.java
@@ -0,0 +1,113 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.core.runtime.internal.adaptor;
+
+import java.io.IOException;
+import java.net.URLConnection;
+import java.util.Properties;
+import org.eclipse.osgi.baseadaptor.*;
+import org.eclipse.osgi.baseadaptor.hooks.AdaptorHook;
+import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor;
+import org.eclipse.osgi.framework.internal.core.FrameworkProperties;
+import org.eclipse.osgi.framework.log.FrameworkLog;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+
+public class EclipseErrorHandler implements AdaptorHook, HookConfigurator {
+ // System property used to prevent VM exit when unexpected errors occur
+ private static final String PROP_EXITONERROR = "eclipse.exitOnError"; //$NON-NLS-1$
+ private BaseAdaptor adaptor;
+
+ /**
+ * @throws BundleException
+ */
+ public void frameworkStart(BundleContext context) throws BundleException {
+ // do nothing
+ }
+
+ /**
+ * @throws BundleException
+ */
+ public void frameworkStop(BundleContext context) throws BundleException {
+ // do nothing
+ }
+
+ public void frameworkStopping(BundleContext context) {
+ // do nothing
+ }
+
+ public void addProperties(Properties properties) {
+ // do nothing
+ }
+
+ /**
+ * @throws IOException
+ */
+ public URLConnection mapLocationToURLConnection(String location) throws IOException {
+ // do nothing
+ return null;
+ }
+
+ private boolean isFatalException(Throwable error) {
+ if (error instanceof VirtualMachineError) {
+ return true;
+ }
+ if (error instanceof ThreadDeath) {
+ return true;
+ }
+ return false;
+ }
+
+ public void handleRuntimeError(Throwable error) {
+ // this is the important method to handle errors
+ boolean exitOnError = false;
+ try {
+ // check the prop each time this happens (should NEVER happen!)
+ exitOnError = Boolean.valueOf(FrameworkProperties.getProperty(EclipseErrorHandler.PROP_EXITONERROR, "true")).booleanValue(); //$NON-NLS-1$
+ String message = EclipseAdaptorMsg.ECLIPSE_ADAPTOR_RUNTIME_ERROR;
+ if (exitOnError && isFatalException(error))
+ message += ' ' + EclipseAdaptorMsg.ECLIPSE_ADAPTOR_EXITING;
+ FrameworkLogEntry logEntry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, message, 0, error, null);
+ adaptor.getFrameworkLog().log(logEntry);
+ } catch (Throwable t) {
+ // we may be in a currupted state and must be able to handle any
+ // errors (ie OutOfMemoryError)
+ // that may occur when handling the first error; this is REALLY the
+ // last resort.
+ try {
+ error.printStackTrace();
+ t.printStackTrace();
+ } catch (Throwable t1) {
+ // if we fail that then we are beyond help.
+ }
+ } finally {
+ // do the exit outside the try block just incase another runtime
+ // error was thrown while logging
+ if (exitOnError && isFatalException(error))
+ System.exit(13);
+ }
+ }
+
+ public void addHooks(HookRegistry hookRegistry) {
+ hookRegistry.addAdaptorHook(this);
+ }
+
+ public FrameworkLog createFrameworkLog() {
+ // do nothing
+ return null;
+ }
+
+ public void initialize(BaseAdaptor initAdaptor) {
+ this.adaptor = initAdaptor;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseLazyStarter.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseLazyStarter.java
new file mode 100644
index 000000000..e1c34d4df
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseLazyStarter.java
@@ -0,0 +1,281 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.core.runtime.internal.adaptor;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.*;
+import org.eclipse.osgi.baseadaptor.*;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
+import org.eclipse.osgi.baseadaptor.hooks.AdaptorHook;
+import org.eclipse.osgi.baseadaptor.hooks.ClassLoadingStatsHook;
+import org.eclipse.osgi.baseadaptor.loader.ClasspathEntry;
+import org.eclipse.osgi.baseadaptor.loader.ClasspathManager;
+import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor;
+import org.eclipse.osgi.framework.adaptor.StatusException;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.framework.internal.core.*;
+import org.eclipse.osgi.framework.internal.core.Constants;
+import org.eclipse.osgi.framework.log.FrameworkLog;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+import org.eclipse.osgi.service.resolver.BundleDescription;
+import org.eclipse.osgi.service.resolver.StateHelper;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.*;
+
+public class EclipseLazyStarter implements ClassLoadingStatsHook, AdaptorHook, HookConfigurator {
+ private static final boolean throwErrorOnFailedStart = "true".equals(FrameworkProperties.getProperty("osgi.compatibility.errorOnFailedStart", "true")); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
+ private BaseAdaptor adaptor;
+ // holds the current activation trigger class and the ClasspathManagers that need to be activated
+ private ThreadLocal<List<Object>> activationStack = new ThreadLocal<List<Object>>();
+ // used to store exceptions that occurred while activating a bundle
+ // keyed by ClasspathManager->Exception
+ // WeakHashMap is used to prevent pinning the ClasspathManager objects.
+ private final Map<ClasspathManager, TerminatingClassNotFoundException> errors = Collections.synchronizedMap(new WeakHashMap<ClasspathManager, TerminatingClassNotFoundException>());
+
+ public void preFindLocalClass(String name, ClasspathManager manager) throws ClassNotFoundException {
+ AbstractBundle bundle = (AbstractBundle) manager.getBaseData().getBundle();
+ // If the bundle is active, uninstalled or stopping then the bundle has already
+ // been initialized (though it may have been destroyed) so just return the class.
+ if ((bundle.getState() & (Bundle.ACTIVE | Bundle.UNINSTALLED | Bundle.STOPPING)) != 0)
+ return;
+ EclipseStorageHook storageHook = (EclipseStorageHook) manager.getBaseData().getStorageHook(EclipseStorageHook.KEY);
+ // The bundle is not active and does not require activation, just return the class
+ if (!shouldActivateFor(name, manager.getBaseData(), storageHook, manager))
+ return;
+ List<Object> stack = activationStack.get();
+ if (stack == null) {
+ stack = new ArrayList<Object>(6);
+ activationStack.set(stack);
+ }
+ // the first element in the stack is the name of the trigger class,
+ // each element after the trigger class is a classpath manager
+ // that must be activated after the trigger class has been defined (see postFindLocalClass)
+ int size = stack.size();
+ if (size > 1) {
+ for (int i = size - 1; i >= 1; i--)
+ if (manager == stack.get(i))
+ // the manager is already on the stack in which case we are already in the process of loading the trigger class
+ return;
+ }
+ Thread threadChangingState = bundle.getStateChanging();
+ if (bundle.getState() == Bundle.STARTING && threadChangingState == Thread.currentThread())
+ return; // this thread is starting the bundle already
+ if (size == 0)
+ stack.add(name);
+ stack.add(manager);
+ }
+
+ public void postFindLocalClass(String name, Class<?> clazz, ClasspathManager manager) throws ClassNotFoundException {
+ List<Object> stack = activationStack.get();
+ if (stack == null)
+ return;
+ int size = stack.size();
+ if (size <= 1 || stack.get(0) != name)
+ return;
+ // if we have a stack we must clear it even if (clazz == null)
+ ClasspathManager[] managers = null;
+ managers = new ClasspathManager[size - 1];
+ for (int i = 1; i < size; i++)
+ managers[i - 1] = (ClasspathManager) stack.get(i);
+ stack.clear();
+ if (clazz == null)
+ return;
+ for (int i = managers.length - 1; i >= 0; i--) {
+ if (errors.get(managers[i]) != null) {
+ if (throwErrorOnFailedStart)
+ throw errors.get(managers[i]);
+ continue;
+ }
+
+ // The bundle must be started.
+ // Note that another thread may already be starting this bundle;
+ // In this case we will timeout after a default of 5 seconds and record the BundleException
+ long startTime = System.currentTimeMillis();
+ try {
+ // do not persist the start of this bundle
+ managers[i].getBaseClassLoader().getDelegate().setLazyTrigger();
+ } catch (BundleException e) {
+ Bundle bundle = managers[i].getBaseData().getBundle();
+ Throwable cause = e.getCause();
+ if (cause != null && cause instanceof StatusException) {
+ StatusException status = (StatusException) cause;
+ if ((status.getStatusCode() & StatusException.CODE_ERROR) == 0) {
+ if (status.getStatus() instanceof Thread) {
+ String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_CLASSLOADER_CONCURRENT_STARTUP, new Object[] {Thread.currentThread(), name, status.getStatus(), bundle, new Long(System.currentTimeMillis() - startTime)});
+ adaptor.getFrameworkLog().log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.WARNING, 0, message, 0, e, null));
+ }
+ continue;
+ }
+ }
+ String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_CLASSLOADER_ACTIVATION, bundle.getSymbolicName(), Long.toString(bundle.getBundleId()));
+ TerminatingClassNotFoundException error = new TerminatingClassNotFoundException(message, e);
+ errors.put(managers[i], error);
+ if (throwErrorOnFailedStart) {
+ adaptor.getFrameworkLog().log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, message, 0, e, null));
+ throw error;
+ }
+ adaptor.getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, bundle, new BundleException(message, e));
+ }
+ }
+ }
+
+ public void preFindLocalResource(String name, ClasspathManager manager) {
+ // do nothing
+ }
+
+ public void postFindLocalResource(String name, URL resource, ClasspathManager manager) {
+ // do nothing
+ }
+
+ public void recordClassDefine(String name, Class<?> clazz, byte[] classbytes, ClasspathEntry classpathEntry, BundleEntry entry, ClasspathManager manager) {
+ // do nothing
+ }
+
+ private boolean shouldActivateFor(String className, BaseData bundledata, EclipseStorageHook storageHook, ClasspathManager manager) throws ClassNotFoundException {
+ if (!isLazyStartable(className, bundledata, storageHook))
+ return false;
+ //if (manager.getBaseClassLoader().getDelegate().isLazyTriggerSet())
+ // return false;
+ // Don't activate non-starting bundles
+ if (bundledata.getBundle().getState() == Bundle.RESOLVED) {
+ if (throwErrorOnFailedStart) {
+ TerminatingClassNotFoundException error = errors.get(manager);
+ if (error != null)
+ throw error;
+ }
+ return (bundledata.getStatus() & Constants.BUNDLE_STARTED) != 0;
+ }
+ return true;
+ }
+
+ private boolean isLazyStartable(String className, BaseData bundledata, EclipseStorageHook storageHook) {
+ if (storageHook == null)
+ return false;
+ boolean lazyStart = storageHook.isLazyStart();
+ String[] excludes = storageHook.getLazyStartExcludes();
+ String[] includes = storageHook.getLazyStartIncludes();
+ // no exceptions, it is easy to figure it out
+ if (excludes == null && includes == null)
+ return lazyStart;
+ // otherwise, we need to check if the package is in the exceptions list
+ int dotPosition = className.lastIndexOf('.');
+ // the class has no package name... no exceptions apply
+ if (dotPosition == -1)
+ return lazyStart;
+ String packageName = className.substring(0, dotPosition);
+ if (lazyStart)
+ return ((includes == null || contains(includes, packageName)) && (excludes == null || !contains(excludes, packageName)));
+ return (excludes != null && contains(excludes, packageName));
+ }
+
+ private boolean contains(String[] array, String element) {
+ for (int i = 0; i < array.length; i++)
+ if (array[i].equals(element))
+ return true;
+ return false;
+ }
+
+ public void addHooks(HookRegistry hookRegistry) {
+ hookRegistry.addClassLoadingStatsHook(this);
+ hookRegistry.addAdaptorHook(this);
+ }
+
+ public void addProperties(Properties properties) {
+ // do nothing
+ }
+
+ public FrameworkLog createFrameworkLog() {
+ // do nothing
+ return null;
+ }
+
+ /**
+ * @throws BundleException
+ */
+ public void frameworkStart(BundleContext context) throws BundleException {
+ // nothing
+ }
+
+ /**
+ * @throws BundleException
+ */
+ public void frameworkStop(BundleContext context) throws BundleException {
+ // nothing
+ }
+
+ public void frameworkStopping(BundleContext context) {
+ if (!Debug.DEBUG_ENABLED)
+ return;
+
+ BundleDescription[] allBundles = adaptor.getState().getResolvedBundles();
+ StateHelper stateHelper = adaptor.getPlatformAdmin().getStateHelper();
+ Object[][] cycles = stateHelper.sortBundles(allBundles);
+ logCycles(cycles);
+ }
+
+ public void handleRuntimeError(Throwable error) {
+ // do nothing
+
+ }
+
+ public void initialize(BaseAdaptor baseAdaptor) {
+ this.adaptor = baseAdaptor;
+ }
+
+ /**
+ * @throws IOException
+ */
+ public URLConnection mapLocationToURLConnection(String location) throws IOException {
+ // do nothing
+ return null;
+ }
+
+ private void logCycles(Object[][] cycles) {
+ // log cycles
+ if (cycles.length > 0) {
+ StringBuffer cycleText = new StringBuffer("["); //$NON-NLS-1$
+ for (int i = 0; i < cycles.length; i++) {
+ cycleText.append('[');
+ for (int j = 0; j < cycles[i].length; j++) {
+ cycleText.append(((BundleDescription) cycles[i][j]).getSymbolicName());
+ cycleText.append(',');
+ }
+ cycleText.insert(cycleText.length() - 1, ']');
+ }
+ cycleText.setCharAt(cycleText.length() - 1, ']');
+ String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_BUNDLESTOPPER_CYCLES_FOUND, cycleText);
+ FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.WARNING, 0, message, 0, null, null);
+ adaptor.getFrameworkLog().log(entry);
+ }
+ }
+
+ private static class TerminatingClassNotFoundException extends ClassNotFoundException implements StatusException {
+ private static final long serialVersionUID = -6730732895632169173L;
+ private Throwable cause;
+
+ public TerminatingClassNotFoundException(String message, Throwable cause) {
+ super(message, cause);
+ this.cause = cause;
+ }
+
+ public Object getStatus() {
+ return cause;
+ }
+
+ public int getStatusCode() {
+ return StatusException.CODE_ERROR;
+ }
+
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseLogFactory.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseLogFactory.java
new file mode 100644
index 000000000..416bbdada
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseLogFactory.java
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.core.runtime.internal.adaptor;
+
+import java.io.*;
+import org.eclipse.equinox.log.Logger;
+import org.eclipse.equinox.log.internal.LogServiceManager;
+import org.eclipse.osgi.framework.log.FrameworkLog;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+import org.osgi.framework.*;
+import org.osgi.service.log.LogService;
+
+public class EclipseLogFactory implements ServiceFactory<FrameworkLog> {
+ final EclipseLogWriter defaultWriter;
+ final LogServiceManager logManager;
+
+ public EclipseLogFactory(EclipseLogWriter defaultWriter, LogServiceManager logManager) {
+ this.defaultWriter = defaultWriter;
+ this.logManager = logManager;
+ }
+
+ public FrameworkLog getService(final Bundle bundle, ServiceRegistration<FrameworkLog> registration) {
+ return createFrameworkLog(bundle, defaultWriter);
+ }
+
+ FrameworkLog createFrameworkLog(Bundle bundle, EclipseLogWriter eclipseWriter) {
+ final EclipseLogWriter logWriter = eclipseWriter == null ? defaultWriter : eclipseWriter;
+ final Logger logger = bundle == null ? logManager.getSystemBundleLog().getLogger(eclipseWriter.getLoggerName()) : logManager.getSystemBundleLog().getLogger(bundle, logWriter.getLoggerName());
+ return new FrameworkLog() {
+
+ public void setWriter(Writer newWriter, boolean append) {
+ logWriter.setWriter(newWriter, append);
+ }
+
+ public void setFile(File newFile, boolean append) throws IOException {
+ logWriter.setFile(newFile, append);
+ }
+
+ public void setConsoleLog(boolean consoleLog) {
+ logWriter.setConsoleLog(consoleLog);
+ }
+
+ public void log(FrameworkLogEntry logEntry) {
+ logger.log(logEntry, convertLevel(logEntry), logEntry.getMessage(), logEntry.getThrowable());
+ }
+
+ public void log(FrameworkEvent frameworkEvent) {
+ Bundle b = frameworkEvent.getBundle();
+ Throwable t = frameworkEvent.getThrowable();
+ String entry = b.getSymbolicName() == null ? b.getLocation() : b.getSymbolicName();
+ int severity;
+ switch (frameworkEvent.getType()) {
+ case FrameworkEvent.INFO :
+ severity = FrameworkLogEntry.INFO;
+ break;
+ case FrameworkEvent.ERROR :
+ severity = FrameworkLogEntry.ERROR;
+ break;
+ case FrameworkEvent.WARNING :
+ severity = FrameworkLogEntry.WARNING;
+ break;
+ default :
+ severity = FrameworkLogEntry.OK;
+ }
+ FrameworkLogEntry logEntry = new FrameworkLogEntry(entry, severity, 0, "", 0, t, null); //$NON-NLS-1$
+ log(logEntry);
+ }
+
+ public File getFile() {
+ return logWriter.getFile();
+ }
+
+ public void close() {
+ logWriter.close();
+ }
+ };
+ }
+
+ public void ungetService(Bundle bundle, ServiceRegistration<FrameworkLog> registration, FrameworkLog service) {
+ // nothing
+ }
+
+ static int convertLevel(FrameworkLogEntry logEntry) {
+ switch (logEntry.getSeverity()) {
+ case FrameworkLogEntry.ERROR :
+ return LogService.LOG_ERROR;
+ case FrameworkLogEntry.WARNING :
+ return LogService.LOG_WARNING;
+ case FrameworkLogEntry.INFO :
+ return LogService.LOG_INFO;
+ case FrameworkLogEntry.OK :
+ return LogService.LOG_DEBUG;
+ case FrameworkLogEntry.CANCEL :
+ default :
+ return 32; // unknown
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseLogHook.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseLogHook.java
new file mode 100644
index 000000000..f4555e6b5
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseLogHook.java
@@ -0,0 +1,147 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.core.runtime.internal.adaptor;
+
+import java.io.*;
+import java.net.URLConnection;
+import java.util.*;
+import org.eclipse.core.runtime.adaptor.EclipseStarter;
+import org.eclipse.core.runtime.adaptor.LocationManager;
+import org.eclipse.equinox.log.internal.LogServiceManager;
+import org.eclipse.osgi.baseadaptor.*;
+import org.eclipse.osgi.baseadaptor.hooks.AdaptorHook;
+import org.eclipse.osgi.framework.internal.core.Constants;
+import org.eclipse.osgi.framework.internal.core.FrameworkProperties;
+import org.eclipse.osgi.framework.log.FrameworkLog;
+import org.eclipse.osgi.internal.baseadaptor.AdaptorUtil;
+import org.eclipse.osgi.service.datalocation.Location;
+import org.osgi.framework.*;
+
+public class EclipseLogHook implements HookConfigurator, AdaptorHook {
+ static final String EQUINOX_LOGGER_NAME = "org.eclipse.equinox.logger"; //$NON-NLS-1$
+ static final String PERF_LOGGER_NAME = "org.eclipse.performance.logger"; //$NON-NLS-1$
+ private static final String PROP_LOG_ENABLED = "eclipse.log.enabled"; //$NON-NLS-1$
+
+ // The eclipse log file extension */
+ private static final String LOG_EXT = ".log"; //$NON-NLS-1$
+ private final LogServiceManager logServiceManager;
+ private final EclipseLogFactory eclipseLogFactory;
+ private final EclipseLogWriter logWriter;
+ private final EclipseLogWriter perfWriter;
+
+ public EclipseLogHook() {
+ String logFileProp = FrameworkProperties.getProperty(EclipseStarter.PROP_LOGFILE);
+ boolean enabled = "true".equals(FrameworkProperties.getProperty(PROP_LOG_ENABLED, "true")); //$NON-NLS-1$ //$NON-NLS-2$
+ if (logFileProp != null) {
+ logWriter = new EclipseLogWriter(new File(logFileProp), EQUINOX_LOGGER_NAME, enabled);
+ } else {
+ Location location = LocationManager.getConfigurationLocation();
+ File configAreaDirectory = null;
+ if (location != null)
+ // TODO assumes the URL is a file: url
+ configAreaDirectory = new File(location.getURL().getFile());
+
+ if (configAreaDirectory != null) {
+ String logFileName = Long.toString(System.currentTimeMillis()) + EclipseLogHook.LOG_EXT;
+ File logFile = new File(configAreaDirectory, logFileName);
+ FrameworkProperties.setProperty(EclipseStarter.PROP_LOGFILE, logFile.getAbsolutePath());
+ logWriter = new EclipseLogWriter(logFile, EQUINOX_LOGGER_NAME, enabled);
+ } else
+ logWriter = new EclipseLogWriter((Writer) null, EQUINOX_LOGGER_NAME, enabled);
+ }
+
+ File logFile = logWriter.getFile();
+ if (logFile != null) {
+ File perfLogFile = new File(logFile.getParentFile(), "performance.log"); //$NON-NLS-1$
+ perfWriter = new EclipseLogWriter(perfLogFile, PERF_LOGGER_NAME, true);
+ } else {
+ perfWriter = new EclipseLogWriter((Writer) null, PERF_LOGGER_NAME, true);
+ }
+ if ("true".equals(FrameworkProperties.getProperty(EclipseStarter.PROP_CONSOLE_LOG))) //$NON-NLS-1$
+ logWriter.setConsoleLog(true);
+ logServiceManager = new LogServiceManager(logWriter, perfWriter);
+ eclipseLogFactory = new EclipseLogFactory(logWriter, logServiceManager);
+
+ }
+
+ private ServiceRegistration<?> frameworkLogReg;
+ private ServiceRegistration<?> perfLogReg;
+
+ public void addHooks(HookRegistry hookRegistry) {
+ hookRegistry.addAdaptorHook(this);
+ }
+
+ public void initialize(BaseAdaptor initAdaptor) {
+ // Nothing
+ }
+
+ /**
+ * @throws BundleException
+ */
+ public void frameworkStart(BundleContext context) throws BundleException {
+ logServiceManager.start(context);
+ frameworkLogReg = AdaptorUtil.register(FrameworkLog.class.getName(), eclipseLogFactory, context);
+ perfLogReg = registerPerformanceLog(context);
+ }
+
+ /**
+ * @throws BundleException
+ */
+ public void frameworkStop(BundleContext context) throws BundleException {
+ frameworkLogReg.unregister();
+ perfLogReg.unregister();
+ logServiceManager.stop(context);
+ }
+
+ public void frameworkStopping(BundleContext context) {
+ // do nothing
+
+ }
+
+ public void addProperties(Properties properties) {
+ // do nothing
+ }
+
+ /**
+ * @throws IOException
+ */
+ public URLConnection mapLocationToURLConnection(String location) throws IOException {
+ // do nothing
+ return null;
+ }
+
+ public void handleRuntimeError(Throwable error) {
+ // do nothing
+ }
+
+ public FrameworkLog createFrameworkLog() {
+ return eclipseLogFactory.createFrameworkLog(null, logWriter);
+ }
+
+ private ServiceRegistration<?> registerPerformanceLog(BundleContext context) {
+ Object service = createPerformanceLog(context.getBundle());
+ String serviceName = FrameworkLog.class.getName();
+ Dictionary<String, Object> serviceProperties = new Hashtable<String, Object>(7);
+ Dictionary<String, String> headers = context.getBundle().getHeaders();
+
+ serviceProperties.put(Constants.SERVICE_VENDOR, headers.get(Constants.BUNDLE_VENDOR));
+ serviceProperties.put(Constants.SERVICE_RANKING, new Integer(Integer.MIN_VALUE));
+ serviceProperties.put(Constants.SERVICE_PID, context.getBundle().getBundleId() + '.' + service.getClass().getName());
+ serviceProperties.put(FrameworkLog.SERVICE_PERFORMANCE, Boolean.TRUE.toString());
+
+ return context.registerService(serviceName, service, serviceProperties);
+ }
+
+ private FrameworkLog createPerformanceLog(Bundle systemBundle) {
+ return eclipseLogFactory.createFrameworkLog(systemBundle, perfWriter);
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseLogWriter.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseLogWriter.java
new file mode 100644
index 000000000..36eab8b08
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseLogWriter.java
@@ -0,0 +1,726 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.runtime.internal.adaptor;
+
+import java.io.*;
+import java.lang.reflect.InvocationTargetException;
+import java.security.AccessController;
+import java.util.Calendar;
+import java.util.Date;
+import org.eclipse.core.runtime.adaptor.EclipseStarter;
+import org.eclipse.equinox.log.*;
+import org.eclipse.osgi.framework.internal.core.FrameworkProperties;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+import org.eclipse.osgi.framework.util.SecureAction;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.service.log.LogEntry;
+import org.osgi.service.log.LogService;
+
+public class EclipseLogWriter implements SynchronousLogListener, LogFilter {
+ private static final String PASSWORD = "-password"; //$NON-NLS-1$
+ /** The session tag */
+ private static final String SESSION = "!SESSION"; //$NON-NLS-1$
+ /** The entry tag */
+ private static final String ENTRY = "!ENTRY"; //$NON-NLS-1$
+ /** The sub-entry tag */
+ private static final String SUBENTRY = "!SUBENTRY"; //$NON-NLS-1$
+ /** The message tag */
+ private static final String MESSAGE = "!MESSAGE"; //$NON-NLS-1$
+ /** The stacktrace tag */
+ private static final String STACK = "!STACK"; //$NON-NLS-1$
+
+ /** The line separator used in the log output */
+ private static final String LINE_SEPARATOR;
+ static {
+ String s = System.getProperty("line.separator"); //$NON-NLS-1$
+ LINE_SEPARATOR = s == null ? "\n" : s; //$NON-NLS-1$
+ }
+ //Constants for rotating log file
+ /** The default size a log file can grow before it is rotated */
+ private static final int DEFAULT_LOG_SIZE = 1000;
+ /** The default number of backup log files */
+ private static final int DEFAULT_LOG_FILES = 10;
+ /** The minimum size limit for log rotation */
+ private static final int LOG_SIZE_MIN = 10;
+
+ /** The system property used to specify the log level */
+ private static final String PROP_LOG_LEVEL = "eclipse.log.level"; //$NON-NLS-1$
+ /** The system property used to specify size a log file can grow before it is rotated */
+ private static final String PROP_LOG_SIZE_MAX = "eclipse.log.size.max"; //$NON-NLS-1$
+ /** The system property used to specify the maximim number of backup log files to use */
+ private static final String PROP_LOG_FILE_MAX = "eclipse.log.backup.max"; //$NON-NLS-1$
+ /** The extension used for log files */
+ private static final String LOG_EXT = ".log"; //$NON-NLS-1$
+ /** The extension markup to use for backup log files*/
+ private static final String BACKUP_MARK = ".bak_"; //$NON-NLS-1$
+
+ /** The system property used to specify command line args should be omitted from the log */
+ private static final String PROP_LOG_INCLUDE_COMMAND_LINE = "eclipse.log.include.commandline"; //$NON-NLS-1$
+ private static final SecureAction secureAction = AccessController.doPrivileged(SecureAction.createSecureAction());
+
+ /** Indicates if the console messages should be printed to the console (System.out) */
+ private boolean consoleLog = false;
+ /** Indicates if the next log message is part of a new session */
+ private boolean newSession = true;
+ /**
+ * The File object to store messages. This value may be null.
+ */
+ private File outFile;
+
+ /**
+ * The Writer to log messages to.
+ */
+ private Writer writer;
+
+ private final String loggerName;
+ private final boolean enabled;
+
+ int maxLogSize = DEFAULT_LOG_SIZE; // The value is in KB.
+ int maxLogFiles = DEFAULT_LOG_FILES;
+ int backupIdx = 0;
+
+ private int logLevel = FrameworkLogEntry.OK;
+ private boolean includeCommandLine = true;
+
+ /**
+ * Constructs an EclipseLog which uses the specified File to log messages to
+ * @param outFile a file to log messages to
+ */
+ public EclipseLogWriter(File outFile, String loggerName, boolean enabled) {
+ this.outFile = outFile;
+ this.writer = null;
+ this.loggerName = loggerName;
+ this.enabled = enabled;
+ readLogProperties();
+ }
+
+ /**
+ * Constructs an EclipseLog which uses the specified Writer to log messages to
+ * @param writer a writer to log messages to
+ */
+ public EclipseLogWriter(Writer writer, String loggerName, boolean enabled) {
+ if (writer == null)
+ // log to System.err by default
+ this.writer = logForStream(System.err);
+ else
+ this.writer = writer;
+ this.loggerName = loggerName;
+ this.enabled = enabled;
+ }
+
+ private Throwable getRoot(Throwable t) {
+ Throwable root = null;
+ if (t instanceof BundleException)
+ root = ((BundleException) t).getNestedException();
+ if (t instanceof InvocationTargetException)
+ root = ((InvocationTargetException) t).getTargetException();
+ // skip inner InvocationTargetExceptions and BundleExceptions
+ if (root instanceof InvocationTargetException || root instanceof BundleException) {
+ Throwable deeplyNested = getRoot(root);
+ if (deeplyNested != null)
+ // if we have something more specific, use it, otherwise keep what we have
+ root = deeplyNested;
+ }
+ return root;
+ }
+
+ /**
+ * Helper method for writing out argument arrays.
+ * @param header the header
+ * @param args the list of arguments
+ */
+ private void writeArgs(String header, String[] args) throws IOException {
+ if (args == null || args.length == 0)
+ return;
+ write(header);
+ for (int i = 0; i < args.length; i++) {
+ //mask out the password argument for security
+ if (i > 0 && PASSWORD.equals(args[i - 1]))
+ write(" (omitted)"); //$NON-NLS-1$
+ else
+ write(" " + args[i]); //$NON-NLS-1$
+ }
+ writeln();
+ }
+
+ /**
+ * Returns the session timestamp. This is the time the platform was started
+ * @return the session timestamp
+ */
+ private String getSessionTimestamp() {
+ // Main should have set the session start-up timestamp so return that.
+ // Return the "now" time if not available.
+ String ts = FrameworkProperties.getProperty("eclipse.startTime"); //$NON-NLS-1$
+ if (ts != null) {
+ try {
+ return getDate(new Date(Long.parseLong(ts)));
+ } catch (NumberFormatException e) {
+ // fall through and use the timestamp from right now
+ }
+ }
+ return getDate(new Date());
+ }
+
+ /**
+ * Writes the session
+ * @throws IOException if an error occurs writing to the log
+ */
+ private void writeSession() throws IOException {
+ write(SESSION);
+ writeSpace();
+ String date = getSessionTimestamp();
+ write(date);
+ writeSpace();
+ for (int i = SESSION.length() + date.length(); i < 78; i++) {
+ write("-"); //$NON-NLS-1$
+ }
+ writeln();
+ // Write out certain values found in System.getProperties()
+ try {
+ String key = "eclipse.buildId"; //$NON-NLS-1$
+ String value = FrameworkProperties.getProperty(key, "unknown"); //$NON-NLS-1$
+ writeln(key + "=" + value); //$NON-NLS-1$
+
+ key = "java.fullversion"; //$NON-NLS-1$
+ value = System.getProperty(key);
+ if (value == null) {
+ key = "java.version"; //$NON-NLS-1$
+ value = System.getProperty(key);
+ writeln(key + "=" + value); //$NON-NLS-1$
+ key = "java.vendor"; //$NON-NLS-1$
+ value = System.getProperty(key);
+ writeln(key + "=" + value); //$NON-NLS-1$
+ } else {
+ writeln(key + "=" + value); //$NON-NLS-1$
+ }
+ } catch (Exception e) {
+ // If we're not allowed to get the values of these properties
+ // then just skip over them.
+ }
+ // The Bootloader has some information that we might be interested in.
+ write("BootLoader constants: OS=" + EclipseEnvironmentInfo.getDefault().getOS()); //$NON-NLS-1$
+ write(", ARCH=" + EclipseEnvironmentInfo.getDefault().getOSArch()); //$NON-NLS-1$
+ write(", WS=" + EclipseEnvironmentInfo.getDefault().getWS()); //$NON-NLS-1$
+ writeln(", NL=" + EclipseEnvironmentInfo.getDefault().getNL()); //$NON-NLS-1$
+ // Add the command-line arguments used to invoke the platform
+ // XXX: this includes runtime-private arguments - should we do that?
+ if (includeCommandLine) {
+ writeArgs("Framework arguments: ", EclipseEnvironmentInfo.getDefault().getNonFrameworkArgs()); //$NON-NLS-1$
+ writeArgs("Command-line arguments: ", EclipseEnvironmentInfo.getDefault().getCommandLineArgs()); //$NON-NLS-1$
+ }
+ }
+
+ public void close() {
+ try {
+ if (writer != null) {
+ Writer tmpWriter = writer;
+ writer = null;
+ tmpWriter.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * If a File is used to log messages to then the File opened and a Writer is created
+ * to log messages to.
+ */
+ private void openFile() {
+ if (writer == null) {
+ if (outFile != null) {
+ try {
+ writer = logForStream(secureAction.getFileOutputStream(outFile, true));
+ } catch (IOException e) {
+ writer = logForStream(System.err);
+ }
+ } else {
+ writer = logForStream(System.err);
+ }
+ }
+ }
+
+ /**
+ * If a File is used to log messages to then the writer is closed.
+ */
+ private void closeFile() {
+ if (outFile != null) {
+ if (writer != null) {
+ try {
+ writer.close();
+ } catch (IOException e) {
+ // we cannot log here; just print the stacktrace.
+ e.printStackTrace();
+ }
+ writer = null;
+ }
+ }
+ }
+
+ private synchronized void log(FrameworkLogEntry logEntry) {
+ if (logEntry == null)
+ return;
+ if (!isLoggable(logEntry.getSeverity()))
+ return;
+ try {
+ checkLogFileSize();
+ openFile();
+ if (newSession) {
+ writeSession();
+ newSession = false;
+ }
+ writeLog(0, logEntry);
+ writer.flush();
+ } catch (Exception e) {
+ // any exceptions during logging should be caught
+ System.err.println("An exception occurred while writing to the platform log:");//$NON-NLS-1$
+ e.printStackTrace(System.err);
+ System.err.println("Logging to the console instead.");//$NON-NLS-1$
+ //we failed to write, so dump log entry to console instead
+ try {
+ writer = logForStream(System.err);
+ writeLog(0, logEntry);
+ writer.flush();
+ } catch (Exception e2) {
+ System.err.println("An exception occurred while logging to the console:");//$NON-NLS-1$
+ e2.printStackTrace(System.err);
+ }
+ } finally {
+ closeFile();
+ }
+ }
+
+ public synchronized void setWriter(Writer newWriter, boolean append) {
+ setOutput(null, newWriter, append);
+ }
+
+ /**
+ * @throws IOException
+ */
+ public synchronized void setFile(File newFile, boolean append) throws IOException {
+ if (newFile != null && !newFile.equals(this.outFile)) {
+ // If it's a new file, then reset.
+ readLogProperties();
+ backupIdx = 0;
+ }
+ setOutput(newFile, null, append);
+ FrameworkProperties.setProperty(EclipseStarter.PROP_LOGFILE, newFile == null ? "" : newFile.getAbsolutePath()); //$NON-NLS-1$
+ }
+
+ public synchronized File getFile() {
+ return outFile;
+ }
+
+ public void setConsoleLog(boolean consoleLog) {
+ this.consoleLog = consoleLog;
+ }
+
+ private void setOutput(File newOutFile, Writer newWriter, boolean append) {
+ if (newOutFile == null || !newOutFile.equals(this.outFile)) {
+ if (this.writer != null) {
+ try {
+ this.writer.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ this.writer = null;
+ }
+ // Append old outFile to newWriter. We only attempt to do this
+ // if the current Writer is backed by a File and this is not
+ // a new session.
+ File oldOutFile = this.outFile;
+ this.outFile = newOutFile;
+ this.writer = newWriter;
+ boolean copyFailed = false;
+ if (append && oldOutFile != null && oldOutFile.isFile()) {
+ Reader fileIn = null;
+ try {
+ openFile();
+ fileIn = new InputStreamReader(secureAction.getFileInputStream(oldOutFile), "UTF-8"); //$NON-NLS-1$
+ copyReader(fileIn, this.writer);
+ } catch (IOException e) {
+ copyFailed = true;
+ e.printStackTrace();
+ } finally {
+ if (fileIn != null) {
+ try {
+ fileIn.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ // delete the old file if copying didn't fail
+ if (!copyFailed)
+ oldOutFile.delete();
+ }
+ closeFile();
+ }
+ }
+ }
+ }
+
+ private void copyReader(Reader reader, Writer aWriter) throws IOException {
+ char buffer[] = new char[1024];
+ int count;
+ while ((count = reader.read(buffer, 0, buffer.length)) > 0) {
+ aWriter.write(buffer, 0, count);
+ }
+ }
+
+ /**
+ * Returns a date string using the correct format for the log.
+ * @param date the Date to format
+ * @return a date string.
+ */
+ private String getDate(Date date) {
+ Calendar c = Calendar.getInstance();
+ c.setTime(date);
+ StringBuffer sb = new StringBuffer();
+ appendPaddedInt(c.get(Calendar.YEAR), 4, sb).append('-');
+ appendPaddedInt(c.get(Calendar.MONTH) + 1, 2, sb).append('-');
+ appendPaddedInt(c.get(Calendar.DAY_OF_MONTH), 2, sb).append(' ');
+ appendPaddedInt(c.get(Calendar.HOUR_OF_DAY), 2, sb).append(':');
+ appendPaddedInt(c.get(Calendar.MINUTE), 2, sb).append(':');
+ appendPaddedInt(c.get(Calendar.SECOND), 2, sb).append('.');
+ appendPaddedInt(c.get(Calendar.MILLISECOND), 3, sb);
+ return sb.toString();
+ }
+
+ private StringBuffer appendPaddedInt(int value, int pad, StringBuffer buffer) {
+ pad = pad - 1;
+ if (pad == 0)
+ return buffer.append(Integer.toString(value));
+ int padding = (int) Math.pow(10, pad);
+ if (value >= padding)
+ return buffer.append(Integer.toString(value));
+ while (padding > value && padding > 1) {
+ buffer.append('0');
+ padding = padding / 10;
+ }
+ buffer.append(value);
+ return buffer;
+ }
+
+ /**
+ * Returns a stacktrace string using the correct format for the log
+ * @param t the Throwable to get the stacktrace for
+ * @return a stacktrace string
+ */
+ private String getStackTrace(Throwable t) {
+ if (t == null)
+ return null;
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+
+ t.printStackTrace(pw);
+ // ensure the root exception is fully logged
+ Throwable root = getRoot(t);
+ if (root != null) {
+ pw.println("Root exception:"); //$NON-NLS-1$
+ root.printStackTrace(pw);
+ }
+ return sw.toString();
+ }
+
+ /**
+ * Returns a Writer for the given OutputStream
+ * @param output an OutputStream to use for the Writer
+ * @return a Writer for the given OutputStream
+ */
+ private Writer logForStream(OutputStream output) {
+ try {
+ return new BufferedWriter(new OutputStreamWriter(output, "UTF-8")); //$NON-NLS-1$
+ } catch (UnsupportedEncodingException e) {
+ return new BufferedWriter(new OutputStreamWriter(output));
+ }
+ }
+
+ /**
+ * Writes the log entry to the log using the specified depth. A depth value of 0
+ * indicates that the log entry is the root entry. Any value greater than 0 indicates
+ * a sub-entry.
+ * @param depth the depth of th entry
+ * @param entry the entry to log
+ * @throws IOException if any error occurs writing to the log
+ */
+ private void writeLog(int depth, FrameworkLogEntry entry) throws IOException {
+ writeEntry(depth, entry);
+ writeMessage(entry);
+ writeStack(entry);
+
+ FrameworkLogEntry[] children = entry.getChildren();
+ if (children != null) {
+ for (int i = 0; i < children.length; i++) {
+ writeLog(depth + 1, children[i]);
+ }
+ }
+ }
+
+ /**
+ * Writes the ENTRY or SUBENTRY header for an entry. A depth value of 0
+ * indicates that the log entry is the root entry. Any value greater than 0 indicates
+ * a sub-entry.
+ * @param depth the depth of th entry
+ * @param entry the entry to write the header for
+ * @throws IOException if any error occurs writing to the log
+ */
+ private void writeEntry(int depth, FrameworkLogEntry entry) throws IOException {
+ if (depth == 0) {
+ writeln(); // write a blank line before all !ENTRY tags bug #64406
+ write(ENTRY);
+ } else {
+ write(SUBENTRY);
+ writeSpace();
+ write(Integer.toString(depth));
+ }
+ writeSpace();
+ write(entry.getEntry());
+ writeSpace();
+ write(Integer.toString(entry.getSeverity()));
+ writeSpace();
+ write(Integer.toString(entry.getBundleCode()));
+ writeSpace();
+ write(getDate(new Date()));
+ writeln();
+ }
+
+ /**
+ * Writes the MESSAGE header to the log for the given entry.
+ * @param entry the entry to write the message for
+ * @throws IOException if any error occurs writing to the log
+ */
+ private void writeMessage(FrameworkLogEntry entry) throws IOException {
+ write(MESSAGE);
+ writeSpace();
+ writeln(entry.getMessage());
+ }
+
+ /**
+ * Writes the STACK header to the log for the given entry.
+ * @param entry the entry to write the stacktrace for
+ * @throws IOException if any error occurs writing to the log
+ */
+ private void writeStack(FrameworkLogEntry entry) throws IOException {
+ Throwable t = entry.getThrowable();
+ if (t != null) {
+ String stack = getStackTrace(t);
+ write(STACK);
+ writeSpace();
+ write(Integer.toString(entry.getStackCode()));
+ writeln();
+ write(stack);
+ }
+ }
+
+ /**
+ * Writes the given message to the log.
+ * @param message the message
+ * @throws IOException if any error occurs writing to the log
+ */
+ private void write(String message) throws IOException {
+ if (message != null) {
+ writer.write(message);
+ if (consoleLog)
+ System.out.print(message);
+ }
+ }
+
+ /**
+ * Writes the given message to the log and a newline.
+ * @param s the message
+ * @throws IOException if any error occurs writing to the log
+ */
+ private void writeln(String s) throws IOException {
+ write(s);
+ writeln();
+ }
+
+ /**
+ * Writes a newline log.
+ * @throws IOException if any error occurs writing to the log
+ */
+ private void writeln() throws IOException {
+ write(LINE_SEPARATOR);
+ }
+
+ /**
+ * Writes a space to the log.
+ * @throws IOException if any error occurs writing to the log
+ */
+ private void writeSpace() throws IOException {
+ write(" "); //$NON-NLS-1$
+ }
+
+ /**
+ * Checks the log file size. If the log file size reaches the limit then the log
+ * is rotated
+ * @return false if an error occured trying to rotate the log
+ */
+ private boolean checkLogFileSize() {
+ if (maxLogSize == 0)
+ return true; // no size limitation.
+
+ boolean isBackupOK = true;
+ if (outFile != null) {
+ if ((secureAction.length(outFile) >> 10) > maxLogSize) { // Use KB as file size unit.
+ String logFilename = outFile.getAbsolutePath();
+
+ // Delete old backup file that will be replaced.
+ String backupFilename = ""; //$NON-NLS-1$
+ if (logFilename.toLowerCase().endsWith(LOG_EXT)) {
+ backupFilename = logFilename.substring(0, logFilename.length() - LOG_EXT.length()) + BACKUP_MARK + backupIdx + LOG_EXT;
+ } else {
+ backupFilename = logFilename + BACKUP_MARK + backupIdx;
+ }
+ File backupFile = new File(backupFilename);
+ if (backupFile.exists()) {
+ if (!backupFile.delete()) {
+ System.err.println("Error when trying to delete old log file: " + backupFile.getName());//$NON-NLS-1$
+ if (backupFile.renameTo(new File(backupFile.getAbsolutePath() + System.currentTimeMillis()))) {
+ System.err.println("So we rename it to filename: " + backupFile.getName()); //$NON-NLS-1$
+ } else {
+ System.err.println("And we also cannot rename it!"); //$NON-NLS-1$
+ isBackupOK = false;
+ }
+ }
+ }
+
+ // Rename current log file to backup one.
+ boolean isRenameOK = outFile.renameTo(backupFile);
+ if (!isRenameOK) {
+ System.err.println("Error when trying to rename log file to backup one."); //$NON-NLS-1$
+ isBackupOK = false;
+ }
+ File newFile = new File(logFilename);
+ setOutput(newFile, null, false);
+
+ // Write a new SESSION header to new log file.
+ openFile();
+ try {
+ writeSession();
+ writeln();
+ writeln("This is a continuation of log file " + backupFile.getAbsolutePath());//$NON-NLS-1$
+ writeln("Created Time: " + getDate(new Date(System.currentTimeMillis()))); //$NON-NLS-1$
+ writer.flush();
+ } catch (IOException ioe) {
+ ioe.printStackTrace(System.err);
+ }
+ closeFile();
+ backupIdx = (++backupIdx) % maxLogFiles;
+ }
+ }
+ return isBackupOK;
+ }
+
+ /**
+ * Reads the PROP_LOG_SIZE_MAX and PROP_LOG_FILE_MAX properties.
+ */
+ private void readLogProperties() {
+ String newMaxLogSize = secureAction.getProperty(PROP_LOG_SIZE_MAX);
+ if (newMaxLogSize != null) {
+ maxLogSize = Integer.parseInt(newMaxLogSize);
+ if (maxLogSize != 0 && maxLogSize < LOG_SIZE_MIN) {
+ // If the value is '0', then it means no size limitation.
+ // Also, make sure no inappropriate(too small) assigned value.
+ maxLogSize = LOG_SIZE_MIN;
+ }
+ }
+
+ String newMaxLogFiles = secureAction.getProperty(PROP_LOG_FILE_MAX);
+ if (newMaxLogFiles != null) {
+ maxLogFiles = Integer.parseInt(newMaxLogFiles);
+ if (maxLogFiles < 1) {
+ // Make sure no invalid assigned value. (at least >= 1)
+ maxLogFiles = DEFAULT_LOG_FILES;
+ }
+ }
+
+ String newLogLevel = secureAction.getProperty(PROP_LOG_LEVEL);
+ if (newLogLevel != null) {
+ if (newLogLevel.equals("ERROR")) //$NON-NLS-1$
+ logLevel = FrameworkLogEntry.ERROR;
+ else if (newLogLevel.equals("WARNING")) //$NON-NLS-1$
+ logLevel = FrameworkLogEntry.ERROR | FrameworkLogEntry.WARNING;
+ else if (newLogLevel.equals("INFO")) //$NON-NLS-1$
+ logLevel = FrameworkLogEntry.INFO | FrameworkLogEntry.ERROR | FrameworkLogEntry.WARNING | FrameworkLogEntry.CANCEL;
+ else
+ logLevel = FrameworkLogEntry.OK; // OK (0) means log everything
+ }
+
+ includeCommandLine = "true".equals(secureAction.getProperty(PROP_LOG_INCLUDE_COMMAND_LINE, "true")); //$NON-NLS-1$//$NON-NLS-2$
+ }
+
+ /**
+ * Determines if the log entry should be logged based on log level.
+ */
+ private boolean isLoggable(int fwkEntrySeverity) {
+ if (logLevel == 0)
+ return true;
+ return (fwkEntrySeverity & logLevel) != 0;
+ }
+
+ public boolean isLoggable(Bundle bundle, String loggableName, int loggableLevel) {
+ if (!enabled)
+ return false;
+ if (loggerName.equals(loggableName))
+ return isLoggable(convertSeverity(loggableLevel));
+ if (EclipseLogHook.PERF_LOGGER_NAME.equals(loggableName))
+ // we don't want to do anything with performance logger unless
+ // this is the performance logger (check done above).
+ return false;
+ if (!EclipseLogHook.EQUINOX_LOGGER_NAME.equals(loggerName))
+ // only the equinox log writer should pay attention to other logs
+ return false;
+ // for now only log errors; probably need this to be configurable
+ return loggableLevel == LogService.LOG_ERROR;
+ }
+
+ public void logged(LogEntry entry) {
+ if (!(entry instanceof ExtendedLogEntry))
+ // TODO this should never happen
+ return;
+ ExtendedLogEntry extended = (ExtendedLogEntry) entry;
+ Object context = extended.getContext();
+ if (context instanceof FrameworkLogEntry) {
+ log((FrameworkLogEntry) context);
+ return;
+ }
+ // OK we are now in a case where someone logged a normal entry to the real LogService
+ log(new FrameworkLogEntry(getFwkEntryTag(entry), convertSeverity(entry.getLevel()), 0, entry.getMessage(), 0, entry.getException(), null));
+ }
+
+ private static String getFwkEntryTag(LogEntry entry) {
+ Bundle b = entry.getBundle();
+ if (b != null && b.getSymbolicName() != null)
+ return b.getSymbolicName();
+ return "unknown"; //$NON-NLS-1$
+ }
+
+ private static int convertSeverity(int entryLevel) {
+ switch (entryLevel) {
+ case LogService.LOG_ERROR :
+ return FrameworkLogEntry.ERROR;
+ case LogService.LOG_WARNING :
+ return FrameworkLogEntry.WARNING;
+ case LogService.LOG_INFO :
+ return FrameworkLogEntry.INFO;
+ case LogService.LOG_DEBUG :
+ return FrameworkLogEntry.OK;
+ default :
+ return 32; // unknown
+ }
+ }
+
+ public String getLoggerName() {
+ return loggerName;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseStorageHook.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseStorageHook.java
new file mode 100644
index 000000000..78aa333aa
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/EclipseStorageHook.java
@@ -0,0 +1,522 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.core.runtime.internal.adaptor;
+
+import java.io.*;
+import java.net.URL;
+import java.security.*;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import org.eclipse.core.runtime.adaptor.LocationManager;
+import org.eclipse.osgi.baseadaptor.*;
+import org.eclipse.osgi.baseadaptor.hooks.StorageHook;
+import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor;
+import org.eclipse.osgi.framework.internal.core.Constants;
+import org.eclipse.osgi.framework.internal.core.FrameworkProperties;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+import org.eclipse.osgi.framework.util.Headers;
+import org.eclipse.osgi.framework.util.KeyedElement;
+import org.eclipse.osgi.internal.baseadaptor.AdaptorUtil;
+import org.eclipse.osgi.service.datalocation.Location;
+import org.eclipse.osgi.service.pluginconversion.PluginConversionException;
+import org.eclipse.osgi.util.ManifestElement;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Version;
+
+public final class EclipseStorageHook implements StorageHook, HookConfigurator {
+ // System property used to check timestamps of the bundles in the configuration
+ private static final String PROP_CHECK_CONFIG = "osgi.checkConfiguration"; //$NON-NLS-1$
+ private static final String PROP_COMPATIBILITY_LAZYSTART = "osgi.compatibility.eagerStart.LazyActivation"; //$NON-NLS-1$
+ private static final boolean COMPATIBILITY_LAZYSTART = Boolean.valueOf(FrameworkProperties.getProperty(PROP_COMPATIBILITY_LAZYSTART, "true")).booleanValue(); //$NON-NLS-1$
+ private static final int STORAGE_VERION = 4;
+
+ public static final String KEY = EclipseStorageHook.class.getName();
+ public static final int HASHCODE = KEY.hashCode();
+
+ private static final byte FLAG_LAZY_START = 0x01;
+ private static final byte FLAG_HAS_PACKAGE_INFO = 0x02;
+ // Note that the 0x04 was used in previous versions, if a new flag is needed then do not reuse this one
+ //private static final byte FLAG_ACTIVATE_ON_CLASSLOAD = 0x04;
+ // Flag to indicate that an include directive is present on the lazy activation policy
+ private static final byte FLAG_HAS_LAZY_INCLUDE = 0x08;
+
+ /** data to detect modification made in the manifest */
+ private long manifestTimeStamp = 0;
+ private byte manifestType = PluginConverterImpl.MANIFEST_TYPE_UNKNOWN;
+
+ private BaseData bundledata;
+
+ /** the Plugin-Class header */
+ private String pluginClass = null;
+ /** Eclipse-LazyStart header */
+ private String[] lazyStartExcludes;
+ private String[] lazyStartIncludes;
+ private int bundleManfestVersion;
+ /** shortcut to know if a bundle has a buddy */
+ private String buddyList;
+ /** shortcut to know if a bundle is a registrant to a registered policy */
+ private String registeredBuddyList;
+ /** DS Service Component header */
+ private String serviceComponent;
+ private byte flags = 0;
+
+ public int getStorageVersion() {
+ return STORAGE_VERION;
+ }
+
+ /**
+ * @throws BundleException
+ */
+ public StorageHook create(BaseData data) throws BundleException {
+ EclipseStorageHook storageHook = new EclipseStorageHook();
+ storageHook.bundledata = data;
+ return storageHook;
+ }
+
+ @SuppressWarnings("deprecation")
+ public void initialize(Dictionary<String, String> manifest) throws BundleException {
+ String activationPolicy = manifest.get(Constants.BUNDLE_ACTIVATIONPOLICY);
+ if (activationPolicy != null) {
+ parseActivationPolicy(this, activationPolicy);
+ } else {
+ String lazyStart = manifest.get(Constants.ECLIPSE_LAZYSTART);
+ if (lazyStart == null)
+ lazyStart = manifest.get(Constants.ECLIPSE_AUTOSTART);
+ parseLazyStart(this, lazyStart);
+ }
+ try {
+ String versionString = manifest.get(Constants.BUNDLE_MANIFESTVERSION);
+ bundleManfestVersion = versionString == null ? 0 : Integer.parseInt(versionString);
+ } catch (NumberFormatException nfe) {
+ bundleManfestVersion = 0;
+ }
+ pluginClass = manifest.get(Constants.PLUGIN_CLASS);
+ buddyList = manifest.get(Constants.BUDDY_LOADER);
+ registeredBuddyList = manifest.get(Constants.REGISTERED_POLICY);
+ if (hasPackageInfo(bundledata.getEntry(Constants.OSGI_BUNDLE_MANIFEST)))
+ flags |= FLAG_HAS_PACKAGE_INFO;
+ String genFrom = manifest.get(PluginConverterImpl.GENERATED_FROM);
+ if (genFrom != null) {
+ ManifestElement generatedFrom = ManifestElement.parseHeader(PluginConverterImpl.GENERATED_FROM, genFrom)[0];
+ if (generatedFrom != null) {
+ manifestTimeStamp = Long.parseLong(generatedFrom.getValue());
+ manifestType = Byte.parseByte(generatedFrom.getAttribute(PluginConverterImpl.MANIFEST_TYPE_ATTRIBUTE));
+ }
+ }
+ if (isAutoStartable()) {
+ bundledata.setStatus(bundledata.getStatus() | Constants.BUNDLE_LAZY_START);
+ if (COMPATIBILITY_LAZYSTART)
+ bundledata.setStatus(bundledata.getStatus() | Constants.BUNDLE_STARTED | Constants.BUNDLE_ACTIVATION_POLICY);
+ }
+ serviceComponent = manifest.get(CachedManifest.SERVICE_COMPONENT);
+ }
+
+ public StorageHook load(BaseData target, DataInputStream in) throws IOException {
+ EclipseStorageHook storageHook = new EclipseStorageHook();
+ storageHook.bundledata = target;
+ storageHook.flags = in.readByte();
+ int pkgCount = in.readInt();
+ String[] packageList = pkgCount > 0 ? new String[pkgCount] : null;
+ for (int i = 0; i < pkgCount; i++)
+ packageList[i] = in.readUTF();
+ storageHook.lazyStartExcludes = packageList;
+ if ((storageHook.flags & FLAG_HAS_LAZY_INCLUDE) != 0) {
+ pkgCount = in.readInt();
+ packageList = pkgCount > 0 ? new String[pkgCount] : null;
+ for (int i = 0; i < pkgCount; i++)
+ packageList[i] = in.readUTF();
+ storageHook.lazyStartIncludes = packageList;
+ }
+ storageHook.buddyList = AdaptorUtil.readString(in, false);
+ storageHook.registeredBuddyList = AdaptorUtil.readString(in, false);
+ storageHook.pluginClass = AdaptorUtil.readString(in, false);
+ storageHook.manifestTimeStamp = in.readLong();
+ storageHook.manifestType = in.readByte();
+ storageHook.bundleManfestVersion = in.readInt();
+ if (storageHook.isAutoStartable()) {
+ if ((target.getStatus() & Constants.BUNDLE_LAZY_START) == 0)
+ target.setStatus(target.getStatus() | Constants.BUNDLE_LAZY_START);
+ // if the compatibility flag is set then we must make sure the persistent start bit is set and the activation policy bit;
+ // if the persistent start bit was already set then we should not set the activation policy bit because this is an "eager" started bundle.
+ if (COMPATIBILITY_LAZYSTART && (target.getStatus() & Constants.BUNDLE_STARTED) == 0)
+ target.setStatus(target.getStatus() | Constants.BUNDLE_STARTED | Constants.BUNDLE_ACTIVATION_POLICY);
+ }
+ storageHook.serviceComponent = AdaptorUtil.readString(in, false);
+ return storageHook;
+ }
+
+ public void save(DataOutputStream out) throws IOException {
+ if (bundledata == null)
+ throw new IllegalStateException();
+ // when this is stored back we always use the has include/exclude flag
+ out.writeByte(flags);
+ String[] excludes = getLazyStartExcludes();
+ if (excludes == null)
+ out.writeInt(0);
+ else {
+ out.writeInt(excludes.length);
+ for (int i = 0; i < excludes.length; i++)
+ out.writeUTF(excludes[i]);
+ }
+ if ((flags & FLAG_HAS_LAZY_INCLUDE) != 0) {
+ String[] includes = getLazyStartIncludes();
+ if (includes == null)
+ out.writeInt(0);
+ else {
+ out.writeInt(includes.length);
+ for (int i = 0; i < includes.length; i++)
+ out.writeUTF(includes[i]);
+ }
+ }
+ AdaptorUtil.writeStringOrNull(out, getBuddyList());
+ AdaptorUtil.writeStringOrNull(out, getRegisteredBuddyList());
+ AdaptorUtil.writeStringOrNull(out, getPluginClass());
+ out.writeLong(getManifestTimeStamp());
+ out.writeByte(getManifestType());
+ out.writeInt(getBundleManifestVersion());
+ AdaptorUtil.writeStringOrNull(out, serviceComponent);
+ }
+
+ public int getKeyHashCode() {
+ return HASHCODE;
+ }
+
+ public boolean compare(KeyedElement other) {
+ return other.getKey() == KEY;
+ }
+
+ public Object getKey() {
+ return KEY;
+ }
+
+ public boolean isLazyStart() {
+ return (flags & FLAG_LAZY_START) == FLAG_LAZY_START;
+ }
+
+ public String[] getLazyStartExcludes() {
+ return lazyStartExcludes;
+ }
+
+ public String[] getLazyStartIncludes() {
+ return lazyStartIncludes;
+ }
+
+ public String getBuddyList() {
+ return buddyList;
+ }
+
+ public boolean hasPackageInfo() {
+ return (flags & FLAG_HAS_PACKAGE_INFO) == FLAG_HAS_PACKAGE_INFO;
+ }
+
+ public String getPluginClass() {
+ return pluginClass;
+ }
+
+ public String getRegisteredBuddyList() {
+ return registeredBuddyList;
+ }
+
+ public long getManifestTimeStamp() {
+ return manifestTimeStamp;
+ }
+
+ public byte getManifestType() {
+ return manifestType;
+ }
+
+ public int getBundleManifestVersion() {
+ return bundleManfestVersion;
+ }
+
+ public String getServiceComponent() {
+ return serviceComponent;
+ }
+
+ /**
+ * Checks whether this bundle is auto started for all resource/class loads or only for a
+ * subset of resource/classloads
+ * @return true if the bundle is auto started; false otherwise
+ */
+ public boolean isAutoStartable() {
+ return isLazyStart() || (lazyStartExcludes != null && lazyStartExcludes.length > 0);
+ }
+
+ private void parseLazyStart(EclipseStorageHook storageHook, String headerValue) {
+ storageHook.lazyStartExcludes = null;
+ ManifestElement[] allElements = null;
+ try {
+ allElements = ManifestElement.parseHeader(Constants.ECLIPSE_LAZYSTART, headerValue);
+ } catch (BundleException e) {
+ // just use the default settings (no auto activation)
+ String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_CLASSLOADER_CANNOT_GET_HEADERS, storageHook.bundledata.getLocation());
+ bundledata.getAdaptor().getFrameworkLog().log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, message, 0, e, null));
+ }
+ //Eclipse-AutoStart not found...
+ if (allElements == null)
+ return;
+ // the single value for this element should be true|false
+ if ("true".equalsIgnoreCase(allElements[0].getValue())) //$NON-NLS-1$
+ storageHook.flags |= FLAG_LAZY_START;
+ // look for any exceptions (the attribute) to the autoActivate setting
+ String[] exceptions = ManifestElement.getArrayFromList(allElements[0].getAttribute(Constants.ECLIPSE_LAZYSTART_EXCEPTIONS));
+ storageHook.lazyStartExcludes = exceptions;
+ }
+
+ private void parseActivationPolicy(EclipseStorageHook storageHook, String headerValue) {
+ storageHook.lazyStartExcludes = null;
+ ManifestElement[] allElements = null;
+ try {
+ allElements = ManifestElement.parseHeader(Constants.BUNDLE_ACTIVATIONPOLICY, headerValue);
+ } catch (BundleException e) {
+ // just use the default settings (no auto activation)
+ String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_CLASSLOADER_CANNOT_GET_HEADERS, storageHook.bundledata.getLocation());
+ bundledata.getAdaptor().getFrameworkLog().log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, message, 0, e, null));
+ }
+ //Bundle-ActivationPolicy not found.
+ if (allElements == null)
+ return;
+ // the single value for this type is lazy
+ if (!Constants.ACTIVATION_LAZY.equalsIgnoreCase(allElements[0].getValue()))
+ return;
+ storageHook.flags |= FLAG_LAZY_START;
+ // look for any include or exclude attrs
+ storageHook.lazyStartExcludes = ManifestElement.getArrayFromList(allElements[0].getDirective(Constants.EXCLUDE_DIRECTIVE));
+ storageHook.lazyStartIncludes = ManifestElement.getArrayFromList(allElements[0].getDirective(Constants.INCLUDE_DIRECTIVE));
+ if (storageHook.lazyStartIncludes != null)
+ storageHook.flags |= FLAG_HAS_LAZY_INCLUDE;
+ }
+
+ // Used to check the bundle manifest file for any package information.
+ // This is used when '.' is on the Bundle-ClassPath to prevent reading
+ // the bundle manifest for pacakge information when loading classes.
+ private static boolean hasPackageInfo(URL url) {
+ if (url == null)
+ return false;
+ BufferedReader br = null;
+ try {
+ br = new BufferedReader(new InputStreamReader(url.openStream()));
+ String line;
+ while ((line = br.readLine()) != null) {
+ if (line.length() < 20)
+ continue;
+ switch (line.charAt(0)) {
+ case 'S' :
+ if (line.charAt(1) == 'p')
+ if (line.startsWith("Specification-Title: ") || line.startsWith("Specification-Version: ") || line.startsWith("Specification-Vendor: ")) //$NON-NLS-1$ //$NON-NLS-2$//$NON-NLS-3$
+ return true;
+ break;
+ case 'I' :
+ if (line.startsWith("Implementation-Title: ") || line.startsWith("Implementation-Version: ") || line.startsWith("Implementation-Vendor: ")) //$NON-NLS-1$ //$NON-NLS-2$//$NON-NLS-3$
+ return true;
+ break;
+ }
+ }
+ } catch (IOException ioe) {
+ // do nothing
+ } finally {
+ if (br != null)
+ try {
+ br.close();
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+ return false;
+ }
+
+ public void addHooks(HookRegistry hookRegistry) {
+ hookRegistry.addStorageHook(this);
+ }
+
+ private void checkTimeStamp() throws IllegalArgumentException {
+ if (!checkManifestTimeStamp())
+ throw new IllegalArgumentException();
+ }
+
+ private boolean checkManifestTimeStamp() {
+ if (!"true".equalsIgnoreCase(FrameworkProperties.getProperty(EclipseStorageHook.PROP_CHECK_CONFIG))) //$NON-NLS-1$
+ return true;
+ if (PluginConverterImpl.getTimeStamp(bundledata.getBundleFile().getBaseFile(), getManifestType()) == getManifestTimeStamp()) {
+ if ((getManifestType() & (PluginConverterImpl.MANIFEST_TYPE_JAR | PluginConverterImpl.MANIFEST_TYPE_BUNDLE)) != 0)
+ return true;
+ String cacheLocation = FrameworkProperties.getProperty(LocationManager.PROP_MANIFEST_CACHE);
+ Location parentConfiguration = LocationManager.getConfigurationLocation().getParentLocation();
+ if (parentConfiguration != null) {
+ try {
+ return checkManifestAndParent(cacheLocation, bundledata.getSymbolicName(), bundledata.getVersion().toString(), getManifestType()) != null;
+ } catch (BundleException e) {
+ return false;
+ }
+ }
+ File cacheFile = new File(cacheLocation, bundledata.getSymbolicName() + '_' + bundledata.getVersion() + ".MF"); //$NON-NLS-1$
+ if (cacheFile.isFile())
+ return true;
+ }
+ return false;
+ }
+
+ private Headers<String, String> checkManifestAndParent(String cacheLocation, String symbolicName, String version, byte inputType) throws BundleException {
+ Headers<String, String> result = basicCheckManifest(cacheLocation, symbolicName, version, inputType);
+ if (result != null)
+ return result;
+ Location parentConfiguration = null;
+ if ((parentConfiguration = LocationManager.getConfigurationLocation().getParentLocation()) != null)
+ result = basicCheckManifest(new File(parentConfiguration.getURL().getFile(), FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME + '/' + LocationManager.MANIFESTS_DIR).toString(), symbolicName, version, inputType);
+ return result;
+ }
+
+ private Headers<String, String> basicCheckManifest(String cacheLocation, String symbolicName, String version, byte inputType) throws BundleException {
+ File currentFile = new File(cacheLocation, symbolicName + '_' + version + ".MF"); //$NON-NLS-1$
+ if (PluginConverterImpl.upToDate(currentFile, bundledata.getBundleFile().getBaseFile(), inputType)) {
+ try {
+ return Headers.parseManifest(new FileInputStream(currentFile));
+ } catch (FileNotFoundException e) {
+ // do nothing.
+ }
+ }
+ return null;
+ }
+
+ Dictionary<String, String> createCachedManifest(boolean firstTime) throws BundleException {
+ return firstTime ? getGeneratedManifest() : new CachedManifest(this);
+ }
+
+ public Dictionary<String, String> getGeneratedManifest() throws BundleException {
+ if (System.getSecurityManager() == null)
+ return getGeneratedManifest0();
+ try {
+ return AccessController.doPrivileged(new PrivilegedExceptionAction<Dictionary<String, String>>() {
+ public Dictionary<String, String> run() throws BundleException {
+ return getGeneratedManifest0();
+ }
+ });
+ } catch (PrivilegedActionException e) {
+ throw (BundleException) e.getException();
+ }
+ }
+
+ final Dictionary<String, String> getGeneratedManifest0() throws BundleException {
+ Dictionary<String, String> builtIn = AdaptorUtil.loadManifestFrom(bundledata);
+ if (builtIn != null) {
+ // the bundle has a built-in manifest - we may not have to generate one
+ if (!isComplete(builtIn)) {
+ Dictionary<String, String> generatedManifest = generateManifest(builtIn);
+ if (generatedManifest != null)
+ return generatedManifest;
+ }
+ // the manifest is complete or we could not complete it - take it as it is
+ manifestType = PluginConverterImpl.MANIFEST_TYPE_BUNDLE;
+ File baseFile = bundledata.getBundleFile().getBaseFile();
+ if (baseFile != null && bundledata.getBundleFile().getBaseFile().isFile()) {
+ manifestTimeStamp = bundledata.getBundleFile().getBaseFile().lastModified();
+ manifestType |= PluginConverterImpl.MANIFEST_TYPE_JAR;
+ } else
+ manifestTimeStamp = bundledata.getBundleFile().getEntry(Constants.OSGI_BUNDLE_MANIFEST).getTime();
+ return builtIn;
+ }
+ Dictionary<String, String> result = generateManifest(null);
+ if (result == null)
+ throw new BundleException(NLS.bind(EclipseAdaptorMsg.ECLIPSE_DATA_MANIFEST_NOT_FOUND, bundledata.getLocation()));
+ return result;
+ }
+
+ private Dictionary<String, String> generateManifest(Dictionary<String, String> builtIn) throws BundleException {
+ String cacheLocation = FrameworkProperties.getProperty(LocationManager.PROP_MANIFEST_CACHE);
+ if (bundledata.getSymbolicName() != null) {
+ Headers<String, String> existingHeaders = checkManifestAndParent(cacheLocation, bundledata.getSymbolicName(), bundledata.getVersion().toString(), manifestType);
+ if (existingHeaders != null)
+ return existingHeaders;
+ }
+
+ PluginConverterImpl converter = PluginConverterImpl.getDefault();
+ if (converter == null)
+ converter = new PluginConverterImpl(bundledata.getAdaptor(), bundledata.getAdaptor().getContext());
+
+ Dictionary<String, String> generatedManifest;
+ try {
+ generatedManifest = converter.convertManifest(bundledata.getBundleFile().getBaseFile(), true, null, true, null);
+ } catch (PluginConversionException pce) {
+ String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_CONVERTER_ERROR_CONVERTING, bundledata.getBundleFile().getBaseFile());
+ throw new BundleException(message, BundleException.MANIFEST_ERROR, pce);
+ }
+
+ //Now we know the symbolicId and the version of the bundle, we check to see if don't have a manifest for it already
+ Version version = Version.parseVersion(generatedManifest.get(Constants.BUNDLE_VERSION));
+ String symbolicName = ManifestElement.parseHeader(org.osgi.framework.Constants.BUNDLE_SYMBOLICNAME, generatedManifest.get(org.osgi.framework.Constants.BUNDLE_SYMBOLICNAME))[0].getValue();
+ ManifestElement generatedFrom = ManifestElement.parseHeader(PluginConverterImpl.GENERATED_FROM, generatedManifest.get(PluginConverterImpl.GENERATED_FROM))[0];
+ Headers<String, String> existingHeaders = checkManifestAndParent(cacheLocation, symbolicName, version.toString(), Byte.parseByte(generatedFrom.getAttribute(PluginConverterImpl.MANIFEST_TYPE_ATTRIBUTE)));
+ //We don't have a manifest.
+ manifestTimeStamp = Long.parseLong(generatedFrom.getValue());
+ manifestType = Byte.parseByte(generatedFrom.getAttribute(PluginConverterImpl.MANIFEST_TYPE_ATTRIBUTE));
+ if (bundledata.getAdaptor().isReadOnly() || existingHeaders != null)
+ return existingHeaders;
+
+ //merge the original manifest with the generated one
+ if (builtIn != null) {
+ Enumeration<String> keysEnum = builtIn.keys();
+ while (keysEnum.hasMoreElements()) {
+ String key = keysEnum.nextElement();
+ generatedManifest.put(key, builtIn.get(key));
+ }
+ }
+
+ //write the generated manifest
+ File bundleManifestLocation = new File(cacheLocation, symbolicName + '_' + version.toString() + ".MF"); //$NON-NLS-1$
+ try {
+ converter.writeManifest(bundleManifestLocation, generatedManifest, true);
+ } catch (Exception e) {
+ //TODO Need to log
+ }
+ return generatedManifest;
+
+ }
+
+ private boolean isComplete(Dictionary<String, String> manifest) {
+ // a manifest is complete if it has a Bundle-SymbolicName entry...
+ if (manifest.get(org.osgi.framework.Constants.BUNDLE_SYMBOLICNAME) != null)
+ return true;
+ // ...or it does not have a plugin/fragment manifest where to get the other entries from
+ return bundledata.getEntry(PluginConverterImpl.PLUGIN_MANIFEST) == null && bundledata.getEntry(PluginConverterImpl.FRAGMENT_MANIFEST) == null;
+ }
+
+ public BaseData getBaseData() {
+ return bundledata;
+ }
+
+ public void copy(StorageHook storageHook) {
+ // copy nothing all must be re-read from a manifest
+ }
+
+ public void validate() throws IllegalArgumentException {
+ checkTimeStamp();
+ }
+
+ public FrameworkAdaptor getAdaptor() {
+ if (bundledata != null)
+ return bundledata.getAdaptor();
+ return null;
+ }
+
+ public Dictionary<String, String> getManifest(boolean firstLoad) throws BundleException {
+ return createCachedManifest(firstLoad);
+ }
+
+ public boolean forgetStatusChange(int status) {
+ return false;
+ }
+
+ public boolean forgetStartLevelChange(int startlevel) {
+ return false;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/IModel.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/IModel.java
new file mode 100644
index 000000000..1a870c822
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/IModel.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.core.runtime.internal.adaptor;
+
+/**
+ * Internal class.
+ */
+public interface IModel {
+ public static final int INDENT = 2;
+ public static final int RADIX = 36;
+
+ public static final String TRUE = "true"; //$NON-NLS-1$
+ public static final String FALSE = "false"; //$NON-NLS-1$
+
+ public static final String REGISTRY = "plugin-registry"; //$NON-NLS-1$
+ public static final String REGISTRY_PATH = "path"; //$NON-NLS-1$
+
+ public static final String FRAGMENT = "fragment"; //$NON-NLS-1$
+ public static final String FRAGMENT_ID = "id"; //$NON-NLS-1$
+ public static final String FRAGMENT_NAME = "name"; //$NON-NLS-1$
+ public static final String FRAGMENT_PROVIDER = "provider-name"; //$NON-NLS-1$
+ public static final String FRAGMENT_VERSION = "version"; //$NON-NLS-1$
+ public static final String FRAGMENT_PLUGIN_ID = "plugin-id"; //$NON-NLS-1$
+ public static final String FRAGMENT_PLUGIN_VERSION = "plugin-version"; //$NON-NLS-1$
+ public static final String FRAGMENT_PLUGIN_MATCH = "match"; //$NON-NLS-1$
+ public static final String FRAGMENT_PLUGIN_MATCH_PERFECT = "perfect"; //$NON-NLS-1$
+ public static final String FRAGMENT_PLUGIN_MATCH_EQUIVALENT = "equivalent"; //$NON-NLS-1$
+ public static final String FRAGMENT_PLUGIN_MATCH_COMPATIBLE = "compatible"; //$NON-NLS-1$
+ public static final String FRAGMENT_PLUGIN_MATCH_GREATER_OR_EQUAL = "greaterOrEqual"; //$NON-NLS-1$
+
+ public static final String PLUGIN = "plugin"; //$NON-NLS-1$
+ public static final String PLUGIN_ID = "id"; //$NON-NLS-1$
+ public static final String PLUGIN_NAME = "name"; //$NON-NLS-1$
+ public static final String PLUGIN_VENDOR = "vendor-name"; //$NON-NLS-1$
+ public static final String PLUGIN_PROVIDER = "provider-name"; //$NON-NLS-1$
+ public static final String PLUGIN_VERSION = "version"; //$NON-NLS-1$
+ public static final String PLUGIN_CLASS = "class"; //$NON-NLS-1$
+
+ public static final String PLUGIN_REQUIRES = "requires"; //$NON-NLS-1$
+ public static final String PLUGIN_REQUIRES_PLATFORM = "platform-version"; //$NON-NLS-1$
+ public static final String PLUGIN_REQUIRES_PLUGIN = "plugin"; //$NON-NLS-1$
+ public static final String PLUGIN_REQUIRES_PLUGIN_VERSION = "version"; //$NON-NLS-1$
+ public static final String PLUGIN_REQUIRES_OPTIONAL = "optional"; //$NON-NLS-1$
+ public static final String PLUGIN_REQUIRES_IMPORT = "import"; //$NON-NLS-1$
+ public static final String PLUGIN_REQUIRES_EXPORT = "export"; //$NON-NLS-1$
+ public static final String PLUGIN_REQUIRES_MATCH = "match"; //$NON-NLS-1$
+ public static final String PLUGIN_REQUIRES_MATCH_EXACT = "exact"; //$NON-NLS-1$
+ public static final String PLUGIN_REQUIRES_MATCH_PERFECT = "perfect"; //$NON-NLS-1$
+ public static final String PLUGIN_REQUIRES_MATCH_EQUIVALENT = "equivalent"; //$NON-NLS-1$
+ public static final String PLUGIN_REQUIRES_MATCH_COMPATIBLE = "compatible"; //$NON-NLS-1$
+ public static final String PLUGIN_REQUIRES_MATCH_GREATER_OR_EQUAL = "greaterOrEqual"; //$NON-NLS-1$
+
+ public static final String PLUGIN_KEY_VERSION_SEPARATOR = "_"; //$NON-NLS-1$
+
+ public static final String RUNTIME = "runtime"; //$NON-NLS-1$
+
+ public static final String LIBRARY = "library"; //$NON-NLS-1$
+ public static final String LIBRARY_NAME = "name"; //$NON-NLS-1$
+ public static final String LIBRARY_SOURCE = "source"; //$NON-NLS-1$
+ public static final String LIBRARY_TYPE = "type"; //$NON-NLS-1$
+ public static final String LIBRARY_EXPORT = "export"; //$NON-NLS-1$
+ public static final String LIBRARY_EXPORT_MASK = "name"; //$NON-NLS-1$
+ public static final String LIBRARY_PACKAGES = "packages"; //$NON-NLS-1$
+ public static final String LIBRARY_PACKAGES_PREFIXES = "prefixes"; //$NON-NLS-1$
+
+ public static final String EXTENSION_POINT = "extension-point"; //$NON-NLS-1$
+ public static final String EXTENSION_POINT_NAME = "name"; //$NON-NLS-1$
+ public static final String EXTENSION_POINT_ID = "id"; //$NON-NLS-1$
+ public static final String EXTENSION_POINT_SCHEMA = "schema"; //$NON-NLS-1$
+
+ public static final String EXTENSION = "extension"; //$NON-NLS-1$
+ public static final String EXTENSION_NAME = "name"; //$NON-NLS-1$
+ public static final String EXTENSION_ID = "id"; //$NON-NLS-1$
+ public static final String EXTENSION_TARGET = "point"; //$NON-NLS-1$
+
+ public static final String ELEMENT = "element"; //$NON-NLS-1$
+ public static final String ELEMENT_NAME = "name"; //$NON-NLS-1$
+ public static final String ELEMENT_VALUE = "value"; //$NON-NLS-1$
+
+ public static final String PROPERTY = "property"; //$NON-NLS-1$
+ public static final String PROPERTY_NAME = "name"; //$NON-NLS-1$
+ public static final String PROPERTY_VALUE = "value"; //$NON-NLS-1$
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/IPluginInfo.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/IPluginInfo.java
new file mode 100644
index 000000000..9520f88eb
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/IPluginInfo.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.runtime.internal.adaptor;
+
+import java.util.*;
+
+/**
+ * Interface used as an entry to the IPluginConverter
+ *
+ * <p>Internal class.</p>
+ */
+public interface IPluginInfo {
+ public Map<String, List<String>> getLibraries();
+
+ public String[] getLibrariesName();
+
+ public ArrayList<PluginParser.Prerequisite> getRequires();
+
+ public String getMasterId();
+
+ public String getMasterVersion();
+
+ public String getMasterMatch();
+
+ public String getPluginClass();
+
+ public String getUniqueId();
+
+ public String getVersion();
+
+ public boolean isFragment();
+
+ public Set<String> getPackageFilters();
+
+ public String getPluginName();
+
+ public String getProviderName();
+
+ public boolean isSingleton();
+
+ public boolean hasExtensionExtensionPoints();
+
+ String validateForm();
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/MessageHelper.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/MessageHelper.java
new file mode 100644
index 000000000..e1cf601b1
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/MessageHelper.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.runtime.internal.adaptor;
+
+import java.util.Date;
+import org.eclipse.osgi.service.resolver.*;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.Constants;
+
+/**
+ * @since 3.3
+ */
+public class MessageHelper {
+ public static String getResolutionFailureMessage(VersionConstraint unsatisfied) {
+ if (unsatisfied.isResolved())
+ throw new IllegalArgumentException();
+ if (unsatisfied instanceof ImportPackageSpecification) {
+ if (ImportPackageSpecification.RESOLUTION_OPTIONAL.equals(((ImportPackageSpecification) unsatisfied).getDirective(Constants.RESOLUTION_DIRECTIVE)))
+ return NLS.bind(EclipseAdaptorMsg.ECLIPSE_MISSING_OPTIONAL_IMPORTED_PACKAGE, toString(unsatisfied));
+ if (ImportPackageSpecification.RESOLUTION_DYNAMIC.equals(((ImportPackageSpecification) unsatisfied).getDirective(Constants.RESOLUTION_DIRECTIVE)))
+ return NLS.bind(EclipseAdaptorMsg.ECLIPSE_MISSING_DYNAMIC_IMPORTED_PACKAGE, toString(unsatisfied));
+ return NLS.bind(EclipseAdaptorMsg.ECLIPSE_MISSING_IMPORTED_PACKAGE, toString(unsatisfied));
+ } else if (unsatisfied instanceof BundleSpecification) {
+ if (((BundleSpecification) unsatisfied).isOptional())
+ return NLS.bind(EclipseAdaptorMsg.ECLIPSE_MISSING_OPTIONAL_REQUIRED_BUNDLE, toString(unsatisfied));
+ return NLS.bind(EclipseAdaptorMsg.ECLIPSE_MISSING_REQUIRED_BUNDLE, toString(unsatisfied));
+ } else if (unsatisfied instanceof HostSpecification) {
+ return NLS.bind(EclipseAdaptorMsg.ECLIPSE_MISSING_HOST, toString(unsatisfied));
+ } else if (unsatisfied instanceof NativeCodeSpecification) {
+ return NLS.bind(EclipseAdaptorMsg.ECLIPSE_MISSING_NATIVECODE, unsatisfied.toString());
+ } else if (unsatisfied instanceof GenericSpecification) {
+ return NLS.bind(EclipseAdaptorMsg.ECLIPSE_MISSING_REQUIRED_CAPABILITY, unsatisfied.toString());
+ }
+ return NLS.bind(EclipseAdaptorMsg.ECLIPSE_MISSING_REQUIREMENT, unsatisfied.toString());
+ }
+
+ /**
+ * Print a debug message to the console.
+ * Pre-pend the message with the current date and the name of the current thread.
+ */
+ public static void debug(String message) {
+ StringBuffer buffer = new StringBuffer();
+ buffer.append(new Date(System.currentTimeMillis()));
+ buffer.append(" - ["); //$NON-NLS-1$
+ buffer.append(Thread.currentThread().getName());
+ buffer.append("] "); //$NON-NLS-1$
+ buffer.append(message);
+ System.out.println(buffer.toString());
+ }
+
+ private static String toString(VersionConstraint constraint) {
+ org.eclipse.osgi.service.resolver.VersionRange versionRange = constraint.getVersionRange();
+ if (versionRange == null)
+ return constraint.getName();
+ return constraint.getName() + '_' + versionRange;
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/PluginConverterImpl.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/PluginConverterImpl.java
new file mode 100644
index 000000000..fe1447f2e
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/PluginConverterImpl.java
@@ -0,0 +1,761 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.runtime.internal.adaptor;
+
+import java.io.*;
+import java.util.*;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import org.eclipse.core.runtime.adaptor.LocationManager;
+import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor;
+import org.eclipse.osgi.framework.internal.core.Constants;
+import org.eclipse.osgi.framework.internal.core.FrameworkProperties;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+import org.eclipse.osgi.internal.baseadaptor.DevClassPathHelper;
+import org.eclipse.osgi.service.pluginconversion.PluginConversionException;
+import org.eclipse.osgi.service.pluginconversion.PluginConverter;
+import org.eclipse.osgi.service.resolver.VersionRange;
+import org.eclipse.osgi.util.ManifestElement;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.*;
+
+/**
+ * Internal class.
+ */
+public class PluginConverterImpl implements PluginConverter {
+ public static boolean DEBUG = false;
+ /** bundle manifest type unknown */
+ static public final byte MANIFEST_TYPE_UNKNOWN = 0x00;
+ /** bundle manifest type bundle (META-INF/MANIFEST.MF) */
+ static public final byte MANIFEST_TYPE_BUNDLE = 0x01;
+ /** bundle manifest type plugin (plugin.xml) */
+ static public final byte MANIFEST_TYPE_PLUGIN = 0x02;
+ /** bundle manifest type fragment (fragment.xml) */
+ static public final byte MANIFEST_TYPE_FRAGMENT = 0x04;
+ /** bundle manifest type jared bundle */
+ static public final byte MANIFEST_TYPE_JAR = 0x08;
+ private static final String SEMICOLON = "; "; //$NON-NLS-1$
+ private static final String UTF_8 = "UTF-8"; //$NON-NLS-1$
+ private static final String LIST_SEPARATOR = ",\n "; //$NON-NLS-1$
+ private static final String LINE_SEPARATOR = "\n "; //$NON-NLS-1$
+ private static final String DOT = "."; //$NON-NLS-1$
+ private static int MAXLINE = 511;
+ private BundleContext context;
+ private FrameworkAdaptor adaptor;
+ private BufferedWriter out;
+ private IPluginInfo pluginInfo;
+ private File pluginManifestLocation;
+ private ZipFile pluginZip;
+ private Dictionary<String, String> generatedManifest;
+ private byte manifestType;
+ private Version target;
+ private Dictionary<String, String> devProperties;
+ static final Version TARGET31 = new Version(3, 1, 0);
+ static final Version TARGET32 = new Version(3, 2, 0);
+ private static final String MANIFEST_VERSION = "Manifest-Version"; //$NON-NLS-1$
+ private static final String PLUGIN_PROPERTIES_FILENAME = "plugin"; //$NON-NLS-1$
+ private static PluginConverterImpl instance;
+ @SuppressWarnings("deprecation")
+ private static final String[] ARCH_LIST = {org.eclipse.osgi.service.environment.Constants.ARCH_PA_RISC, org.eclipse.osgi.service.environment.Constants.ARCH_PPC, org.eclipse.osgi.service.environment.Constants.ARCH_SPARC, org.eclipse.osgi.service.environment.Constants.ARCH_X86, org.eclipse.osgi.service.environment.Constants.ARCH_AMD64, org.eclipse.osgi.service.environment.Constants.ARCH_IA64};
+ static public final String FRAGMENT_MANIFEST = "fragment.xml"; //$NON-NLS-1$
+ static public final String GENERATED_FROM = "Generated-from"; //$NON-NLS-1$
+ static public final String MANIFEST_TYPE_ATTRIBUTE = "type"; //$NON-NLS-1$
+ private static final String[] OS_LIST = {org.eclipse.osgi.service.environment.Constants.OS_AIX, org.eclipse.osgi.service.environment.Constants.OS_HPUX, org.eclipse.osgi.service.environment.Constants.OS_LINUX, org.eclipse.osgi.service.environment.Constants.OS_MACOSX, org.eclipse.osgi.service.environment.Constants.OS_QNX, org.eclipse.osgi.service.environment.Constants.OS_SOLARIS, org.eclipse.osgi.service.environment.Constants.OS_WIN32};
+ protected static final String PI_RUNTIME = "org.eclipse.core.runtime"; //$NON-NLS-1$
+ protected static final String PI_BOOT = "org.eclipse.core.boot"; //$NON-NLS-1$
+ protected static final String PI_RUNTIME_COMPATIBILITY = "org.eclipse.core.runtime.compatibility"; //$NON-NLS-1$
+ static public final String PLUGIN_MANIFEST = "plugin.xml"; //$NON-NLS-1$
+ private static final String COMPATIBILITY_ACTIVATOR = "org.eclipse.core.internal.compatibility.PluginActivator"; //$NON-NLS-1$
+ private static final String[] WS_LIST = {org.eclipse.osgi.service.environment.Constants.WS_CARBON, org.eclipse.osgi.service.environment.Constants.WS_GTK, org.eclipse.osgi.service.environment.Constants.WS_MOTIF, org.eclipse.osgi.service.environment.Constants.WS_PHOTON, org.eclipse.osgi.service.environment.Constants.WS_WIN32};
+ private static final String IGNORE_DOT = "@ignoredot@"; //$NON-NLS-1$
+
+ public static PluginConverterImpl getDefault() {
+ return instance;
+ }
+
+ public PluginConverterImpl(FrameworkAdaptor adaptor, BundleContext context) {
+ this.context = context;
+ this.adaptor = adaptor;
+ instance = this;
+ }
+
+ private void init() {
+ // need to make sure these fields are cleared out for each conversion.
+ out = null;
+ pluginInfo = null;
+ pluginManifestLocation = null;
+ pluginZip = null;
+ generatedManifest = new Hashtable<String, String>(10);
+ manifestType = MANIFEST_TYPE_UNKNOWN;
+ target = null;
+ devProperties = null;
+ }
+
+ private void fillPluginInfo(File pluginBaseLocation) throws PluginConversionException {
+ pluginManifestLocation = pluginBaseLocation;
+ if (pluginManifestLocation == null)
+ throw new IllegalArgumentException();
+ InputStream pluginFile = null;
+ try {
+ try {
+ pluginFile = findPluginManifest(pluginBaseLocation);
+ } catch (IOException e) {
+ throw new PluginConversionException(NLS.bind(EclipseAdaptorMsg.ECLIPSE_CONVERTER_FILENOTFOUND, pluginBaseLocation.getAbsolutePath()), e);
+ }
+ if (pluginFile == null)
+ throw new PluginConversionException(NLS.bind(EclipseAdaptorMsg.ECLIPSE_CONVERTER_FILENOTFOUND, pluginBaseLocation.getAbsolutePath()));
+ pluginInfo = parsePluginInfo(pluginFile);
+ } finally {
+ if (pluginZip != null)
+ try {
+ pluginZip.close();
+ pluginZip = null;
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ String validation = pluginInfo.validateForm();
+ if (validation != null)
+ throw new PluginConversionException(validation);
+ }
+
+ private Set<String> filterExport(Set<String> exportToFilter, Collection<String> filter) {
+ if (filter == null || filter.contains("*")) //$NON-NLS-1$
+ return exportToFilter;
+ Set<String> filteredExport = new HashSet<String>(exportToFilter.size());
+ for (String anExport : exportToFilter) {
+ for (String aFilter : filter) {
+ int dotStar = aFilter.indexOf(".*"); //$NON-NLS-1$
+ if (dotStar != -1)
+ aFilter = aFilter.substring(0, dotStar);
+ if (anExport.equals(aFilter)) {
+ filteredExport.add(anExport);
+ break;
+ }
+ }
+ }
+ return filteredExport;
+ }
+
+ private List<String> findOSJars(File pluginRoot, String path, boolean filter) {
+ path = path.substring(4);
+ List<String> found = new ArrayList<String>(0);
+ for (int i = 0; i < OS_LIST.length; i++) {
+ //look for os/osname/path
+ String searchedPath = "os/" + OS_LIST[i] + "/" + path; //$NON-NLS-1$ //$NON-NLS-2$
+ if (new File(pluginRoot, searchedPath).exists())
+ found.add(searchedPath + (filter ? ";(os=" + WS_LIST[i] + ")" : "")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ //look for os/osname/archname/path
+ for (int j = 0; j < ARCH_LIST.length; j++) {
+ searchedPath = "os/" + OS_LIST[i] + "/" + ARCH_LIST[j] + "/" + path; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ if (new File(pluginRoot, searchedPath).exists()) {
+ found.add(searchedPath + (filter ? ";(& (os=" + WS_LIST[i] + ") (arch=" + ARCH_LIST[j] + ")" : "")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ }
+ }
+ }
+ return found;
+ }
+
+ private InputStream findPluginManifest(File baseLocation) throws IOException {
+ //Here, we can not use the bundlefile because it may explode the jar and returns a location from which we will not be able to derive the jars location
+ if (pluginZip != null)
+ try {
+ pluginZip.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ pluginZip = null;
+ if (!baseLocation.isDirectory()) {
+ manifestType |= MANIFEST_TYPE_JAR;
+ pluginZip = new ZipFile(baseLocation);
+ }
+
+ if (pluginZip != null) {
+ ZipEntry manifestEntry = pluginZip.getEntry(PLUGIN_MANIFEST);
+ if (manifestEntry != null) {
+ manifestType |= MANIFEST_TYPE_PLUGIN;
+ return pluginZip.getInputStream(manifestEntry);
+ }
+ } else {
+ File manifestFile = new File(baseLocation, PLUGIN_MANIFEST);
+ if (manifestFile.exists()) {
+ manifestType |= MANIFEST_TYPE_PLUGIN;
+ return new FileInputStream(manifestFile);
+ }
+ }
+
+ if (pluginZip != null) {
+ ZipEntry manifestEntry = pluginZip.getEntry(FRAGMENT_MANIFEST);
+ if (manifestEntry != null) {
+ manifestType |= MANIFEST_TYPE_PLUGIN;
+ return pluginZip.getInputStream(manifestEntry);
+ }
+ } else {
+ File manifestFile = new File(baseLocation, FRAGMENT_MANIFEST);
+ if (manifestFile.exists()) {
+ manifestType |= MANIFEST_TYPE_FRAGMENT;
+ return new FileInputStream(manifestFile);
+ }
+ }
+
+ return null;
+ }
+
+ private List<String> findWSJars(File pluginRoot, String path, boolean filter) {
+ path = path.substring(4);
+ List<String> found = new ArrayList<String>(0);
+ for (int i = 0; i < WS_LIST.length; i++) {
+ String searchedPath = "ws/" + WS_LIST[i] + path; //$NON-NLS-1$
+ if (new File(pluginRoot, searchedPath).exists()) {
+ found.add(searchedPath + (filter ? ";(ws=" + WS_LIST[i] + ")" : "")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ }
+ return found;
+ }
+
+ protected void fillManifest(boolean compatibilityManifest, boolean analyseJars) {
+ generateManifestVersion();
+ generateHeaders();
+ generateClasspath();
+ generateActivator();
+ generatePluginClass();
+ if (analyseJars)
+ generateProvidePackage();
+ generateRequireBundle();
+ generateLocalizationEntry();
+ generateEclipseHeaders();
+ if (compatibilityManifest) {
+ generateTimestamp();
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ public void writeManifest(File generationLocation, Dictionary<String, String> manifestToWrite, boolean compatibilityManifest) throws PluginConversionException {
+ long start = System.currentTimeMillis();
+ try {
+ File parentFile = new File(generationLocation.getParent());
+ parentFile.mkdirs();
+ generationLocation.createNewFile();
+ if (!generationLocation.isFile()) {
+ String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_CONVERTER_ERROR_CREATING_BUNDLE_MANIFEST, this.pluginInfo.getUniqueId(), generationLocation);
+ throw new PluginConversionException(message);
+ }
+ // replaces any eventual existing file
+ manifestToWrite = new Hashtable<String, String>((Hashtable) manifestToWrite);
+ // MANIFEST.MF files must be written using UTF-8
+ out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(generationLocation), UTF_8));
+ writeEntry(MANIFEST_VERSION, manifestToWrite.remove(MANIFEST_VERSION));
+ writeEntry(GENERATED_FROM, manifestToWrite.remove(GENERATED_FROM)); //Need to do this first uptoDate check expect the generated-from tag to be in the first line
+ // always attempt to write the Bundle-ManifestVersion header if it exists (bug 109863)
+ writeEntry(Constants.BUNDLE_MANIFESTVERSION, manifestToWrite.remove(Constants.BUNDLE_MANIFESTVERSION));
+ writeEntry(Constants.BUNDLE_NAME, manifestToWrite.remove(Constants.BUNDLE_NAME));
+ writeEntry(Constants.BUNDLE_SYMBOLICNAME, manifestToWrite.remove(Constants.BUNDLE_SYMBOLICNAME));
+ writeEntry(Constants.BUNDLE_VERSION, manifestToWrite.remove(Constants.BUNDLE_VERSION));
+ writeEntry(Constants.BUNDLE_CLASSPATH, manifestToWrite.remove(Constants.BUNDLE_CLASSPATH));
+ writeEntry(Constants.BUNDLE_ACTIVATOR, manifestToWrite.remove(Constants.BUNDLE_ACTIVATOR));
+ writeEntry(Constants.BUNDLE_VENDOR, manifestToWrite.remove(Constants.BUNDLE_VENDOR));
+ writeEntry(Constants.FRAGMENT_HOST, manifestToWrite.remove(Constants.FRAGMENT_HOST));
+ writeEntry(Constants.BUNDLE_LOCALIZATION, manifestToWrite.remove(Constants.BUNDLE_LOCALIZATION));
+ // always attempt to write the Export-Package header if it exists (bug 109863)
+ writeEntry(Constants.EXPORT_PACKAGE, manifestToWrite.remove(Constants.EXPORT_PACKAGE));
+ // always attempt to write the Provide-Package header if it exists (bug 109863)
+ writeEntry(Constants.PROVIDE_PACKAGE, manifestToWrite.remove(Constants.PROVIDE_PACKAGE));
+ writeEntry(Constants.REQUIRE_BUNDLE, manifestToWrite.remove(Constants.REQUIRE_BUNDLE));
+ Enumeration<String> keys = manifestToWrite.keys();
+ while (keys.hasMoreElements()) {
+ String key = keys.nextElement();
+ writeEntry(key, manifestToWrite.get(key));
+ }
+ out.flush();
+ } catch (IOException e) {
+ String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_CONVERTER_ERROR_CREATING_BUNDLE_MANIFEST, this.pluginInfo.getUniqueId(), generationLocation);
+ throw new PluginConversionException(message, e);
+ } finally {
+ if (out != null)
+ try {
+ out.close();
+ } catch (IOException e) {
+ // only report problems writing to/flushing the file
+ }
+ }
+ if (DEBUG)
+ System.out.println("Time to write out converted manifest to: " + generationLocation + ": " + (System.currentTimeMillis() - start) + "ms."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ private void generateLocalizationEntry() {
+ generatedManifest.put(Constants.BUNDLE_LOCALIZATION, PLUGIN_PROPERTIES_FILENAME);
+ }
+
+ private void generateManifestVersion() {
+ generatedManifest.put(MANIFEST_VERSION, "1.0"); //$NON-NLS-1$
+ }
+
+ private boolean requireRuntimeCompatibility() {
+ ArrayList<PluginParser.Prerequisite> requireList = pluginInfo.getRequires();
+ for (Iterator<PluginParser.Prerequisite> iter = requireList.iterator(); iter.hasNext();) {
+ if (iter.next().getName().equalsIgnoreCase(PI_RUNTIME_COMPATIBILITY))
+ return true;
+ }
+ return false;
+ }
+
+ private void generateActivator() {
+ if (!pluginInfo.isFragment())
+ if (!requireRuntimeCompatibility()) {
+ String pluginClass = pluginInfo.getPluginClass();
+ if (pluginClass != null && !pluginClass.trim().equals("")) //$NON-NLS-1$
+ generatedManifest.put(Constants.BUNDLE_ACTIVATOR, pluginClass);
+ } else {
+ generatedManifest.put(Constants.BUNDLE_ACTIVATOR, COMPATIBILITY_ACTIVATOR);
+ }
+ }
+
+ private void generateClasspath() {
+ String[] classpath = pluginInfo.getLibrariesName();
+ if (classpath.length != 0)
+ generatedManifest.put(Constants.BUNDLE_CLASSPATH, getStringFromArray(classpath, LIST_SEPARATOR));
+ }
+
+ private void generateHeaders() {
+ if (TARGET31.compareTo(target) <= 0)
+ generatedManifest.put(Constants.BUNDLE_MANIFESTVERSION, "2"); //$NON-NLS-1$
+ generatedManifest.put(Constants.BUNDLE_NAME, pluginInfo.getPluginName());
+ generatedManifest.put(Constants.BUNDLE_VERSION, pluginInfo.getVersion());
+ generatedManifest.put(Constants.BUNDLE_SYMBOLICNAME, getSymbolicNameEntry());
+ String provider = pluginInfo.getProviderName();
+ if (provider != null)
+ generatedManifest.put(Constants.BUNDLE_VENDOR, provider);
+ if (pluginInfo.isFragment()) {
+ StringBuffer hostBundle = new StringBuffer();
+ hostBundle.append(pluginInfo.getMasterId());
+ String versionRange = getVersionRange(pluginInfo.getMasterVersion(), pluginInfo.getMasterMatch()); // TODO need to get match rule here!
+ if (versionRange != null)
+ hostBundle.append(versionRange);
+ generatedManifest.put(Constants.FRAGMENT_HOST, hostBundle.toString());
+ }
+ }
+
+ /*
+ * Generates an entry in the form:
+ * <symbolic-name>[; singleton=true]
+ */
+ private String getSymbolicNameEntry() {
+ // false is the default, so don't bother adding anything
+ if (!pluginInfo.isSingleton())
+ return pluginInfo.getUniqueId();
+ StringBuffer result = new StringBuffer(pluginInfo.getUniqueId());
+ result.append(SEMICOLON);
+ result.append(Constants.SINGLETON_DIRECTIVE);
+ String assignment = TARGET31.compareTo(target) <= 0 ? ":=" : "="; //$NON-NLS-1$ //$NON-NLS-2$
+ result.append(assignment).append("true"); //$NON-NLS-1$
+ return result.toString();
+ }
+
+ private void generatePluginClass() {
+ if (requireRuntimeCompatibility()) {
+ String pluginClass = pluginInfo.getPluginClass();
+ if (pluginClass != null)
+ generatedManifest.put(Constants.PLUGIN_CLASS, pluginClass);
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ private void generateProvidePackage() {
+ Set<String> exports = getExports();
+ if (exports != null && exports.size() != 0) {
+ generatedManifest.put(TARGET31.compareTo(target) <= 0 ? Constants.EXPORT_PACKAGE : Constants.PROVIDE_PACKAGE, getStringFromCollection(exports, LIST_SEPARATOR));
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ private void generateRequireBundle() {
+ ArrayList<PluginParser.Prerequisite> requiredBundles = pluginInfo.getRequires();
+ if (requiredBundles.size() == 0)
+ return;
+ StringBuffer bundleRequire = new StringBuffer();
+ for (Iterator<PluginParser.Prerequisite> iter = requiredBundles.iterator(); iter.hasNext();) {
+ PluginParser.Prerequisite element = iter.next();
+ StringBuffer modImport = new StringBuffer(element.getName());
+ String versionRange = getVersionRange(element.getVersion(), element.getMatch());
+ if (versionRange != null)
+ modImport.append(versionRange);
+ if (element.isExported()) {
+ if (TARGET31.compareTo(target) <= 0)
+ modImport.append(';').append(Constants.VISIBILITY_DIRECTIVE).append(":=").append(Constants.VISIBILITY_REEXPORT);//$NON-NLS-1$
+ else
+ modImport.append(';').append(Constants.REPROVIDE_ATTRIBUTE).append("=true");//$NON-NLS-1$
+ }
+ if (element.isOptional()) {
+ if (TARGET31.compareTo(target) <= 0)
+ modImport.append(';').append(Constants.RESOLUTION_DIRECTIVE).append(":=").append(Constants.RESOLUTION_OPTIONAL);//$NON-NLS-1$
+ else
+ modImport.append(';').append(Constants.OPTIONAL_ATTRIBUTE).append("=true");//$NON-NLS-1$
+ }
+ bundleRequire.append(modImport.toString());
+ if (iter.hasNext())
+ bundleRequire.append(LIST_SEPARATOR);
+ }
+ generatedManifest.put(Constants.REQUIRE_BUNDLE, bundleRequire.toString());
+ }
+
+ private void generateTimestamp() {
+ // so it is easy to tell which ones are generated
+ generatedManifest.put(GENERATED_FROM, Long.toString(getTimeStamp(pluginManifestLocation, manifestType)) + ";" + MANIFEST_TYPE_ATTRIBUTE + "=" + manifestType); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ @SuppressWarnings("deprecation")
+ private void generateEclipseHeaders() {
+ if (pluginInfo.isFragment())
+ return;
+
+ String pluginClass = pluginInfo.getPluginClass();
+ if (pluginInfo.hasExtensionExtensionPoints() || (pluginClass != null && !pluginClass.trim().equals(""))) //$NON-NLS-1$
+ generatedManifest.put(TARGET32.compareTo(target) <= 0 ? Constants.ECLIPSE_LAZYSTART : Constants.ECLIPSE_AUTOSTART, "true"); //$NON-NLS-1$
+ }
+
+ private Set<String> getExports() {
+ Map<String, List<String>> libs = pluginInfo.getLibraries();
+ if (libs == null)
+ return null;
+
+ //If we are in dev mode, then add the binary folders on the list libs with the export clause set to be the cumulation of the export clause of the real libs
+ if (devProperties != null || DevClassPathHelper.inDevelopmentMode()) {
+ String[] devClassPath = DevClassPathHelper.getDevClassPath(pluginInfo.getUniqueId(), devProperties);
+ // collect export clauses
+ List<String> allExportClauses = new ArrayList<String>(libs.size());
+ Set<Map.Entry<String, List<String>>> libEntries = libs.entrySet();
+ for (Iterator<Map.Entry<String, List<String>>> iter = libEntries.iterator(); iter.hasNext();) {
+ Map.Entry<String, List<String>> element = iter.next();
+ allExportClauses.addAll(element.getValue());
+ }
+ if (devClassPath != null) {
+ // bug 88498
+ // if there is a devClassPath defined for this plugin and the @ignoredot@ flag is true
+ // then we will ignore the '.' library specified in the plugin.xml
+ String[] ignoreDotProp = DevClassPathHelper.getDevClassPath(IGNORE_DOT, devProperties);
+ if (devClassPath.length > 0 && ignoreDotProp != null && ignoreDotProp.length > 0 && "true".equals(ignoreDotProp[0])) //$NON-NLS-1$
+ libs.remove(DOT);
+ for (int i = 0; i < devClassPath.length; i++)
+ libs.put(devClassPath[i], allExportClauses);
+ }
+ }
+
+ Set<String> result = new TreeSet<String>();
+ Set<Map.Entry<String, List<String>>> libEntries = libs.entrySet();
+ for (Iterator<Map.Entry<String, List<String>>> iter = libEntries.iterator(); iter.hasNext();) {
+ Map.Entry<String, List<String>> element = iter.next();
+ List<String> filter = element.getValue();
+ if (filter.size() == 0) //If the library is not exported, then ignore it
+ continue;
+ String libEntryText = element.getKey().trim();
+ File libraryLocation;
+ if (libEntryText.equals(DOT))
+ libraryLocation = pluginManifestLocation;
+ else {
+ // in development time, libEntries may contain absolute locations (linked folders)
+ File libEntryAsPath = new File(libEntryText);
+ libraryLocation = libEntryAsPath.isAbsolute() ? libEntryAsPath : new File(pluginManifestLocation, libEntryText);
+ }
+ Set<String> exports = null;
+ if (libraryLocation.exists()) {
+ if (libraryLocation.isFile())
+ exports = filterExport(getExportsFromJAR(libraryLocation), filter); //TODO Need to handle $xx$ variables
+ else if (libraryLocation.isDirectory())
+ exports = filterExport(getExportsFromDir(libraryLocation), filter);
+ } else {
+ List<String> expandedLibs = getLibrariesExpandingVariables(element.getKey(), false);
+ exports = new HashSet<String>();
+ for (Iterator<String> iterator = expandedLibs.iterator(); iterator.hasNext();) {
+ String libName = iterator.next();
+ File libFile = new File(pluginManifestLocation, libName);
+ if (libFile.isFile()) {
+ exports.addAll(filterExport(getExportsFromJAR(libFile), filter));
+ }
+ }
+ }
+ if (exports != null)
+ result.addAll(exports);
+ }
+ return result;
+ }
+
+ private Set<String> getExportsFromDir(File location) {
+ return getExportsFromDir(location, ""); //$NON-NLS-1$
+ }
+
+ private Set<String> getExportsFromDir(File location, String packageName) {
+ String prefix = (packageName.length() > 0) ? (packageName + '.') : ""; //$NON-NLS-1$
+ String[] files = location.list();
+ Set<String> exportedPaths = new HashSet<String>();
+ boolean containsFile = false;
+ if (files != null)
+ for (int i = 0; i < files.length; i++) {
+ if (!isValidPackageName(files[i]))
+ continue;
+ File pkgFile = new File(location, files[i]);
+ if (pkgFile.isDirectory())
+ exportedPaths.addAll(getExportsFromDir(pkgFile, prefix + files[i]));
+ else
+ containsFile = true;
+ }
+ if (containsFile)
+ // Allow the default package to be provided. If the default package
+ // contains a File then use "." as the package name to provide for default.
+ if (packageName.length() > 0)
+ exportedPaths.add(packageName);
+ else
+ exportedPaths.add(DOT);
+ return exportedPaths;
+ }
+
+ private Set<String> getExportsFromJAR(File jarFile) {
+ Set<String> names = new HashSet<String>();
+ ZipFile file = null;
+ try {
+ file = new ZipFile(jarFile);
+ } catch (IOException e) {
+ String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_CONVERTER_PLUGIN_LIBRARY_IGNORED, jarFile, pluginInfo.getUniqueId());
+ adaptor.getFrameworkLog().log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, message, 0, e, null));
+ return names;
+ }
+ //Run through the entries
+ for (Enumeration<? extends ZipEntry> entriesEnum = file.entries(); entriesEnum.hasMoreElements();) {
+ ZipEntry entry = entriesEnum.nextElement();
+ String name = entry.getName();
+ if (!isValidPackageName(name))
+ continue;
+ int lastSlash = name.lastIndexOf("/"); //$NON-NLS-1$
+ //Ignore folders that do not contain files
+ if (lastSlash != -1) {
+ if (lastSlash != name.length() - 1 && name.lastIndexOf(' ') == -1)
+ names.add(name.substring(0, lastSlash).replace('/', '.'));
+ } else {
+ // Allow the default package to be provided. If the default package
+ // contains a File then use "." as the package name to provide for default.
+ names.add(DOT);
+ }
+ }
+ try {
+ file.close();
+ } catch (IOException e) {
+ // Nothing to do
+ }
+ return names;
+ }
+
+ private List<String> getLibrariesExpandingVariables(String libraryPath, boolean filter) {
+ String var = hasPrefix(libraryPath);
+ if (var == null) {
+ List<String> returnValue = new ArrayList<String>(1);
+ returnValue.add(libraryPath);
+ return returnValue;
+ }
+ if (var.equals("ws")) { //$NON-NLS-1$
+ return findWSJars(pluginManifestLocation, libraryPath, filter);
+ }
+ if (var.equals("os")) { //$NON-NLS-1$
+ return findOSJars(pluginManifestLocation, libraryPath, filter);
+ }
+ return new ArrayList<String>(0);
+ }
+
+ //return a String representing the string found between the $s
+ private String hasPrefix(String libPath) {
+ if (libPath.startsWith("$ws$")) //$NON-NLS-1$
+ return "ws"; //$NON-NLS-1$
+ if (libPath.startsWith("$os$")) //$NON-NLS-1$
+ return "os"; //$NON-NLS-1$
+ if (libPath.startsWith("$nl$")) //$NON-NLS-1$
+ return "nl"; //$NON-NLS-1$
+ return null;
+ }
+
+ private boolean isValidPackageName(String name) {
+ if (name.indexOf(' ') > 0 || name.equalsIgnoreCase("META-INF") || name.startsWith("META-INF/")) //$NON-NLS-1$ //$NON-NLS-2$
+ return false;
+ return true;
+ }
+
+ /**
+ * Parses the plugin manifest to find out: - the plug-in unique identifier -
+ * the plug-in version - runtime/libraries entries - the plug-in class -
+ * the master plugin (for a fragment)
+ */
+ private IPluginInfo parsePluginInfo(InputStream pluginLocation) throws PluginConversionException {
+ InputStream input = null;
+ try {
+ input = new BufferedInputStream(pluginLocation);
+ return new PluginParser(adaptor, context, target).parsePlugin(input);
+ } catch (Exception e) {
+ String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_CONVERTER_ERROR_PARSING_PLUGIN_MANIFEST, pluginManifestLocation);
+ throw new PluginConversionException(message, e);
+ } finally {
+ if (input != null)
+ try {
+ input.close();
+ } catch (IOException e) {
+ //ignore exception
+ }
+ }
+ }
+
+ public static boolean upToDate(File generationLocation, File pluginLocation, byte manifestType) {
+ if (!generationLocation.isFile())
+ return false;
+ String secondLine = null;
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(new FileInputStream(generationLocation)));
+ reader.readLine();
+ secondLine = reader.readLine();
+ } catch (IOException e) {
+ // not a big deal - we could not read an existing manifest
+ return false;
+ } finally {
+ if (reader != null)
+ try {
+ reader.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ String tag = GENERATED_FROM + ": "; //$NON-NLS-1$
+ if (secondLine == null || !secondLine.startsWith(tag))
+ return false;
+
+ secondLine = secondLine.substring(tag.length());
+ ManifestElement generatedFrom;
+ try {
+ generatedFrom = ManifestElement.parseHeader(PluginConverterImpl.GENERATED_FROM, secondLine)[0];
+ } catch (BundleException be) {
+ return false;
+ }
+ String timestampStr = generatedFrom.getValue();
+ try {
+ return Long.parseLong(timestampStr.trim()) == getTimeStamp(pluginLocation, manifestType);
+ } catch (NumberFormatException nfe) {
+ // not a big deal - just a bogus existing manifest that will be ignored
+ }
+ return false;
+ }
+
+ public static long getTimeStamp(File pluginLocation, byte manifestType) {
+ if ((manifestType & MANIFEST_TYPE_JAR) != 0)
+ return pluginLocation.lastModified();
+ else if ((manifestType & MANIFEST_TYPE_PLUGIN) != 0)
+ return new File(pluginLocation, PLUGIN_MANIFEST).lastModified();
+ else if ((manifestType & MANIFEST_TYPE_FRAGMENT) != 0)
+ return new File(pluginLocation, FRAGMENT_MANIFEST).lastModified();
+ else if ((manifestType & MANIFEST_TYPE_BUNDLE) != 0)
+ return new File(pluginLocation, Constants.OSGI_BUNDLE_MANIFEST).lastModified();
+ return -1;
+ }
+
+ private void writeEntry(String key, String value) throws IOException {
+ if (value != null && value.length() > 0) {
+ out.write(splitOnComma(key + ": " + value)); //$NON-NLS-1$
+ out.write('\n');
+ }
+ }
+
+ private String splitOnComma(String value) {
+ if (value.length() < MAXLINE || value.indexOf(LINE_SEPARATOR) >= 0)
+ return value; // assume the line is already split
+ String[] values = ManifestElement.getArrayFromList(value);
+ if (values == null || values.length == 0)
+ return value;
+ StringBuffer sb = new StringBuffer(value.length() + ((values.length - 1) * LIST_SEPARATOR.length()));
+ for (int i = 0; i < values.length - 1; i++)
+ sb.append(values[i]).append(LIST_SEPARATOR);
+ sb.append(values[values.length - 1]);
+ return sb.toString();
+ }
+
+ private String getStringFromArray(String[] values, String separator) {
+ if (values == null)
+ return ""; //$NON-NLS-1$
+ StringBuffer result = new StringBuffer();
+ for (int i = 0; i < values.length; i++) {
+ if (i > 0)
+ result.append(separator);
+ result.append(values[i]);
+ }
+ return result.toString();
+ }
+
+ private String getStringFromCollection(Collection<String> collection, String separator) {
+ StringBuffer result = new StringBuffer();
+ boolean first = true;
+ for (Iterator<String> i = collection.iterator(); i.hasNext();) {
+ if (first)
+ first = false;
+ else
+ result.append(separator);
+ result.append(i.next());
+ }
+ return result.toString();
+ }
+
+ public synchronized Dictionary<String, String> convertManifest(File pluginBaseLocation, boolean compatibility, String targetVersion, boolean analyseJars, Dictionary<String, String> devProps) throws PluginConversionException {
+ long start = System.currentTimeMillis();
+ if (DEBUG)
+ System.out.println("Convert " + pluginBaseLocation); //$NON-NLS-1$
+ init();
+ this.target = targetVersion == null ? TARGET32 : new Version(targetVersion);
+ this.devProperties = devProps;
+ fillPluginInfo(pluginBaseLocation);
+ fillManifest(compatibility, analyseJars);
+ if (DEBUG)
+ System.out.println("Time to convert manifest for: " + pluginBaseLocation + ": " + (System.currentTimeMillis() - start) + "ms."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ return generatedManifest;
+ }
+
+ public synchronized File convertManifest(File pluginBaseLocation, File bundleManifestLocation, boolean compatibilityManifest, String targetVersion, boolean analyseJars, Dictionary<String, String> devProps) throws PluginConversionException {
+ convertManifest(pluginBaseLocation, compatibilityManifest, targetVersion, analyseJars, devProps);
+ if (bundleManifestLocation == null) {
+ String cacheLocation = FrameworkProperties.getProperty(LocationManager.PROP_MANIFEST_CACHE);
+ bundleManifestLocation = new File(cacheLocation, pluginInfo.getUniqueId() + '_' + pluginInfo.getVersion() + ".MF"); //$NON-NLS-1$
+ }
+ if (upToDate(bundleManifestLocation, pluginManifestLocation, manifestType))
+ return bundleManifestLocation;
+ writeManifest(bundleManifestLocation, generatedManifest, compatibilityManifest);
+ return bundleManifestLocation;
+ }
+
+ private String getVersionRange(String reqVersion, String matchRule) {
+ if (reqVersion == null)
+ return null;
+
+ Version minVersion = Version.parseVersion(reqVersion);
+ String versionRange;
+ if (matchRule != null) {
+ if (matchRule.equalsIgnoreCase(IModel.PLUGIN_REQUIRES_MATCH_PERFECT)) {
+ versionRange = new VersionRange(minVersion, true, minVersion, true).toString();
+ } else if (matchRule.equalsIgnoreCase(IModel.PLUGIN_REQUIRES_MATCH_EQUIVALENT)) {
+ versionRange = new VersionRange(minVersion, true, new Version(minVersion.getMajor(), minVersion.getMinor() + 1, 0, ""), false).toString(); //$NON-NLS-1$
+ } else if (matchRule.equalsIgnoreCase(IModel.PLUGIN_REQUIRES_MATCH_COMPATIBLE)) {
+ versionRange = new VersionRange(minVersion, true, new Version(minVersion.getMajor() + 1, 0, 0, ""), false).toString(); //$NON-NLS-1$
+ } else if (matchRule.equalsIgnoreCase(IModel.PLUGIN_REQUIRES_MATCH_GREATER_OR_EQUAL)) {
+ // just return the reqVersion here without any version range
+ versionRange = reqVersion;
+ } else {
+ versionRange = new VersionRange(minVersion, true, new Version(minVersion.getMajor() + 1, 0, 0, ""), false).toString(); //$NON-NLS-1$
+ }
+ } else {
+ versionRange = new VersionRange(minVersion, true, new Version(minVersion.getMajor() + 1, 0, 0, ""), false).toString(); //$NON-NLS-1$
+ }
+
+ StringBuffer result = new StringBuffer();
+ result.append(';').append(Constants.BUNDLE_VERSION_ATTRIBUTE).append('=');
+ result.append('\"').append(versionRange).append('\"');
+ return result.toString();
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/PluginParser.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/PluginParser.java
new file mode 100644
index 000000000..1448b28ff
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/PluginParser.java
@@ -0,0 +1,710 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.runtime.internal.adaptor;
+
+import java.io.InputStream;
+import java.util.*;
+import javax.xml.parsers.SAXParserFactory;
+import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Version;
+import org.osgi.util.tracker.ServiceTracker;
+import org.xml.sax.*;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Internal class.
+ */
+public class PluginParser extends DefaultHandler implements IModel {
+ private static ServiceTracker<SAXParserFactory, SAXParserFactory> xmlTracker = null;
+
+ private PluginInfo manifestInfo = new PluginInfo();
+ private BundleContext context;
+ private FrameworkAdaptor adaptor;
+ Version target; // The targeted platform for the given manifest
+ static final Version TARGET21 = new Version(2, 1, 0);
+
+ public class PluginInfo implements IPluginInfo {
+ String schemaVersion;
+ String pluginId;
+ String version;
+ String vendor;
+
+ // an ordered list of library path names.
+ List<String> libraryPaths;
+ // TODO Should get rid of the libraries map and just have a
+ // list of library export statements instead. Library paths must
+ // preserve order.
+ Map<String, List<String>> libraries; //represent the libraries and their export statement
+ ArrayList<PluginParser.Prerequisite> requires;
+ private boolean requiresExpanded = false; //indicates if the requires have been processed.
+ boolean compatibilityFound = false; //set to true is the requirement list contain compatilibity
+ String pluginClass;
+ String masterPluginId;
+ String masterVersion;
+ String masterMatch;
+ private Set<String> filters;
+ String pluginName;
+ boolean singleton;
+ boolean fragment;
+ private final static String TARGET21_STRING = "2.1"; //$NON-NLS-1$
+ boolean hasExtensionExtensionPoints = false;
+
+ public boolean isFragment() {
+ return fragment;
+ }
+
+ public String toString() {
+ return "plugin-id: " + pluginId + " version: " + version + " libraries: " + libraries + " class:" + pluginClass + " master: " + masterPluginId + " master-version: " + masterVersion + " requires: " + requires + " singleton: " + singleton; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$
+ }
+
+ public Map<String, List<String>> getLibraries() {
+ if (libraries == null)
+ return new HashMap<String, List<String>>(0);
+ return libraries;
+ }
+
+ public ArrayList<Prerequisite> getRequires() {
+ if (!TARGET21.equals(target) && schemaVersion == null && !requiresExpanded) {
+ requiresExpanded = true;
+ if (requires == null) {
+ requires = new ArrayList<Prerequisite>(1);
+ requires.add(new Prerequisite(PluginConverterImpl.PI_RUNTIME, TARGET21_STRING, false, false, IModel.PLUGIN_REQUIRES_MATCH_GREATER_OR_EQUAL));
+ requires.add(new Prerequisite(PluginConverterImpl.PI_RUNTIME_COMPATIBILITY, null, false, false, null));
+ } else {
+ //Add elements on the requirement list of ui and help.
+ for (int i = 0; i < requires.size(); i++) {
+ Prerequisite analyzed = requires.get(i);
+ if ("org.eclipse.ui".equals(analyzed.getName())) { //$NON-NLS-1$
+ requires.add(i + 1, new Prerequisite("org.eclipse.ui.workbench.texteditor", null, true, analyzed.isExported(), null)); //$NON-NLS-1$
+ requires.add(i + 1, new Prerequisite("org.eclipse.jface.text", null, true, analyzed.isExported(), null)); //$NON-NLS-1$
+ requires.add(i + 1, new Prerequisite("org.eclipse.ui.editors", null, true, analyzed.isExported(), null)); //$NON-NLS-1$
+ requires.add(i + 1, new Prerequisite("org.eclipse.ui.views", null, true, analyzed.isExported(), null)); //$NON-NLS-1$
+ requires.add(i + 1, new Prerequisite("org.eclipse.ui.ide", null, true, analyzed.isExported(), null)); //$NON-NLS-1$
+ } else if ("org.eclipse.help".equals(analyzed.getName())) { //$NON-NLS-1$
+ requires.add(i + 1, new Prerequisite("org.eclipse.help.base", null, true, analyzed.isExported(), null)); //$NON-NLS-1$
+ } else if (PluginConverterImpl.PI_RUNTIME.equals(analyzed.getName()) && !compatibilityFound) {
+ requires.add(i + 1, new Prerequisite(PluginConverterImpl.PI_RUNTIME_COMPATIBILITY, null, false, analyzed.isExported(), null));
+ }
+ }
+ if (!requires.contains(new Prerequisite(PluginConverterImpl.PI_RUNTIME_COMPATIBILITY, null, false, false, null))) {
+ requires.add(new Prerequisite(PluginConverterImpl.PI_RUNTIME_COMPATIBILITY, null, false, false, null));
+ }
+ //Remove any prereq on runtime and add a prereq on runtime 2.1
+ //This is used to recognize the version for which the given plugin was initially targeted.
+ Prerequisite runtimePrereq = new Prerequisite(PluginConverterImpl.PI_RUNTIME, null, false, false, null);
+ requires.remove(runtimePrereq);
+ requires.add(new Prerequisite(PluginConverterImpl.PI_RUNTIME, TARGET21_STRING, false, false, IModel.PLUGIN_REQUIRES_MATCH_GREATER_OR_EQUAL));
+ }
+ }
+ if (requires == null)
+ return requires = new ArrayList<Prerequisite>(0);
+
+ return requires;
+ }
+
+ public String getMasterId() {
+ return masterPluginId;
+ }
+
+ public String getMasterVersion() {
+ return masterVersion;
+ }
+
+ public String getMasterMatch() {
+ return masterMatch;
+ }
+
+ public String getPluginClass() {
+ return pluginClass;
+ }
+
+ public String getUniqueId() {
+ return pluginId;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public Set<String> getPackageFilters() {
+ return filters;
+ }
+
+ public String[] getLibrariesName() {
+ if (libraryPaths == null)
+ return new String[0];
+ return libraryPaths.toArray(new String[libraryPaths.size()]);
+ }
+
+ public String getPluginName() {
+ return pluginName;
+ }
+
+ public String getProviderName() {
+ return vendor;
+ }
+
+ public boolean isSingleton() {
+ return singleton;
+ }
+
+ public boolean hasExtensionExtensionPoints() {
+ return hasExtensionExtensionPoints;
+ }
+
+ public String getRoot() {
+ return isFragment() ? FRAGMENT : PLUGIN;
+ }
+
+ /*
+ * Provides some basic form of validation. Since plugin/fragment is the only mandatory
+ * attribute, it is the only one we cara about here.
+ */
+ public String validateForm() {
+ if (this.pluginId == null)
+ return NLS.bind(EclipseAdaptorMsg.ECLIPSE_CONVERTER_MISSING_ATTRIBUTE, new String[] {getRoot(), PLUGIN_ID, getRoot()});
+ if (this.pluginName == null)
+ return NLS.bind(EclipseAdaptorMsg.ECLIPSE_CONVERTER_MISSING_ATTRIBUTE, new String[] {getRoot(), PLUGIN_NAME, getRoot()});
+ if (this.version == null)
+ return NLS.bind(EclipseAdaptorMsg.ECLIPSE_CONVERTER_MISSING_ATTRIBUTE, new String[] {getRoot(), PLUGIN_VERSION, getRoot()});
+ if (isFragment() && this.masterPluginId == null)
+ return NLS.bind(EclipseAdaptorMsg.ECLIPSE_CONVERTER_MISSING_ATTRIBUTE, new String[] {getRoot(), FRAGMENT_PLUGIN_ID, getRoot()});
+ if (isFragment() && this.masterVersion == null)
+ return NLS.bind(EclipseAdaptorMsg.ECLIPSE_CONVERTER_MISSING_ATTRIBUTE, new String[] {getRoot(), FRAGMENT_PLUGIN_VERSION, getRoot()});
+ return null;
+ }
+ }
+
+ // Current State Information
+ Stack<Integer> stateStack = new Stack<Integer>();
+
+ // Current object stack (used to hold the current object we are populating in this plugin info
+ Stack<Object> objectStack = new Stack<Object>();
+ Locator locator = null;
+
+ // Valid States
+ private static final int IGNORED_ELEMENT_STATE = 0;
+ private static final int INITIAL_STATE = 1;
+ private static final int PLUGIN_STATE = 2;
+ private static final int PLUGIN_RUNTIME_STATE = 3;
+ private static final int PLUGIN_REQUIRES_STATE = 4;
+ private static final int PLUGIN_EXTENSION_POINT_STATE = 5;
+ private static final int PLUGIN_EXTENSION_STATE = 6;
+ private static final int RUNTIME_LIBRARY_STATE = 7;
+ private static final int LIBRARY_EXPORT_STATE = 8;
+ private static final int PLUGIN_REQUIRES_IMPORT_STATE = 9;
+ private static final int FRAGMENT_STATE = 11;
+
+ public PluginParser(FrameworkAdaptor adaptor, BundleContext context, Version target) {
+ super();
+ this.context = context;
+ this.adaptor = adaptor;
+ this.target = target;
+ }
+
+ /**
+ * Receive a Locator object for document events.
+ *
+ * <p>
+ * By default, do nothing. Application writers may override this method in
+ * a subclass if they wish to store the locator for use with other document
+ * events.
+ * </p>
+ *
+ * @param locator A locator for all SAX document events.
+ * @see org.xml.sax.ContentHandler#setDocumentLocator(org.xml.sax.Locator)
+ * @see org.xml.sax.Locator
+ */
+ public void setDocumentLocator(Locator locator) {
+ this.locator = locator;
+ }
+
+ public void endDocument() {
+ // nothing
+ }
+
+ public void endElement(String uri, String elementName, String qName) {
+ switch (stateStack.peek().intValue()) {
+ case IGNORED_ELEMENT_STATE :
+ stateStack.pop();
+ break;
+ case INITIAL_STATE :
+ // shouldn't get here
+ // internalError(Policy.bind("parse.internalStack", elementName)); //$NON-NLS-1$
+ break;
+ case PLUGIN_STATE :
+ case FRAGMENT_STATE :
+ break;
+ case PLUGIN_RUNTIME_STATE :
+ if (elementName.equals(RUNTIME)) {
+ stateStack.pop();
+ }
+ break;
+ case PLUGIN_REQUIRES_STATE :
+ if (elementName.equals(PLUGIN_REQUIRES)) {
+ stateStack.pop();
+ objectStack.pop();
+ }
+ break;
+ case PLUGIN_EXTENSION_POINT_STATE :
+ if (elementName.equals(EXTENSION_POINT)) {
+ stateStack.pop();
+ }
+ break;
+ case PLUGIN_EXTENSION_STATE :
+ if (elementName.equals(EXTENSION)) {
+ stateStack.pop();
+ }
+ break;
+ case RUNTIME_LIBRARY_STATE :
+ if (elementName.equals(LIBRARY)) {
+ String curLibrary = (String) objectStack.pop();
+ if (!curLibrary.trim().equals("")) { //$NON-NLS-1$
+ @SuppressWarnings("unchecked")
+ List<String> exports = (List<String>) objectStack.pop();
+ if (manifestInfo.libraries == null) {
+ manifestInfo.libraries = new HashMap<String, List<String>>(3);
+ manifestInfo.libraryPaths = new ArrayList<String>(3);
+ }
+ manifestInfo.libraries.put(curLibrary, exports);
+ manifestInfo.libraryPaths.add(curLibrary.replace('\\', '/'));
+ }
+ stateStack.pop();
+ }
+ break;
+ case LIBRARY_EXPORT_STATE :
+ if (elementName.equals(LIBRARY_EXPORT)) {
+ stateStack.pop();
+ }
+ break;
+ case PLUGIN_REQUIRES_IMPORT_STATE :
+ if (elementName.equals(PLUGIN_REQUIRES_IMPORT)) {
+ stateStack.pop();
+ }
+ break;
+ }
+ }
+
+ public void error(SAXParseException ex) {
+ logStatus(ex);
+ }
+
+ public void fatalError(SAXParseException ex) throws SAXException {
+ logStatus(ex);
+ throw ex;
+ }
+
+ public void handleExtensionPointState(String elementName, Attributes attributes) {
+ // nothing to do for extension-points' children
+ stateStack.push(new Integer(IGNORED_ELEMENT_STATE));
+ manifestInfo.hasExtensionExtensionPoints = true;
+ }
+
+ public void handleExtensionState(String elementName, Attributes attributes) {
+ // nothing to do for extensions' children
+ stateStack.push(new Integer(IGNORED_ELEMENT_STATE));
+ manifestInfo.hasExtensionExtensionPoints = true;
+ }
+
+ public void handleInitialState(String elementName, Attributes attributes) {
+ if (elementName.equals(PLUGIN)) {
+ stateStack.push(new Integer(PLUGIN_STATE));
+ parsePluginAttributes(attributes);
+ } else if (elementName.equals(FRAGMENT)) {
+ manifestInfo.fragment = true;
+ stateStack.push(new Integer(FRAGMENT_STATE));
+ parseFragmentAttributes(attributes);
+ } else {
+ stateStack.push(new Integer(IGNORED_ELEMENT_STATE));
+ internalError(elementName);
+ }
+ }
+
+ public void handleLibraryExportState(String elementName, Attributes attributes) {
+ // All elements ignored.
+ stateStack.push(new Integer(IGNORED_ELEMENT_STATE));
+ }
+
+ public void handleLibraryState(String elementName, Attributes attributes) {
+ if (elementName.equals(LIBRARY_EXPORT)) {
+ // Change State
+ stateStack.push(new Integer(LIBRARY_EXPORT_STATE));
+ // The top element on the stack much be a library element
+ String currentLib = (String) objectStack.peek();
+ if (attributes == null)
+ return;
+ String maskValue = attributes.getValue("", LIBRARY_EXPORT_MASK); //$NON-NLS-1$
+ // pop off the library - already in currentLib
+ objectStack.pop();
+ @SuppressWarnings("unchecked")
+ List<String> exportMask = (List<String>) objectStack.peek();
+ // push library back on
+ objectStack.push(currentLib);
+ //Split the export upfront
+ if (maskValue != null) {
+ StringTokenizer tok = new StringTokenizer(maskValue, ","); //$NON-NLS-1$
+ while (tok.hasMoreTokens()) {
+ String value = tok.nextToken();
+ if (!exportMask.contains(maskValue))
+ exportMask.add(value.trim());
+ }
+ }
+ return;
+ }
+ if (elementName.equals(LIBRARY_PACKAGES)) {
+ stateStack.push(new Integer(IGNORED_ELEMENT_STATE));
+ return;
+ }
+ stateStack.push(new Integer(IGNORED_ELEMENT_STATE));
+ internalError(elementName);
+ return;
+ }
+
+ public void handlePluginState(String elementName, Attributes attributes) {
+ if (elementName.equals(RUNTIME)) {
+ // We should only have one Runtime element in a plugin or fragment
+ Object whatIsIt = objectStack.peek();
+ if ((whatIsIt instanceof PluginInfo) && ((PluginInfo) objectStack.peek()).libraries != null) {
+ // This is at least the 2nd Runtime element we have hit. Ignore it.
+ stateStack.push(new Integer(IGNORED_ELEMENT_STATE));
+ return;
+ }
+ stateStack.push(new Integer(PLUGIN_RUNTIME_STATE));
+ // Push a new vector to hold all the library entries objectStack.push(new Vector());
+ return;
+ }
+ if (elementName.equals(PLUGIN_REQUIRES)) {
+ stateStack.push(new Integer(PLUGIN_REQUIRES_STATE));
+ // Push a new vector to hold all the prerequisites
+ objectStack.push(new ArrayList<String>());
+ parseRequiresAttributes(attributes);
+ return;
+ }
+ if (elementName.equals(EXTENSION_POINT)) {
+ // mark the plugin as singleton and ignore all elements under extension (if there are any)
+ manifestInfo.singleton = true;
+ stateStack.push(new Integer(PLUGIN_EXTENSION_POINT_STATE));
+ return;
+ }
+ if (elementName.equals(EXTENSION)) {
+ // mark the plugin as singleton and ignore all elements under extension (if there are any)
+ manifestInfo.singleton = true;
+ stateStack.push(new Integer(PLUGIN_EXTENSION_STATE));
+ return;
+ }
+ // If we get to this point, the element name is one we don't currently accept.
+ // Set the state to indicate that this element will be ignored
+ stateStack.push(new Integer(IGNORED_ELEMENT_STATE));
+ internalError(elementName);
+ }
+
+ public void handleRequiresImportState(String elementName, Attributes attributes) {
+ // All elements ignored.
+ stateStack.push(new Integer(IGNORED_ELEMENT_STATE));
+ }
+
+ public void handleRequiresState(String elementName, Attributes attributes) {
+ if (elementName.equals(PLUGIN_REQUIRES_IMPORT)) {
+ parsePluginRequiresImport(attributes);
+ return;
+ }
+ // If we get to this point, the element name is one we don't currently accept.
+ // Set the state to indicate that this element will be ignored
+ stateStack.push(new Integer(IGNORED_ELEMENT_STATE));
+ internalError(elementName);
+ }
+
+ public void handleRuntimeState(String elementName, Attributes attributes) {
+ if (elementName.equals(LIBRARY)) {
+ // Change State
+ stateStack.push(new Integer(RUNTIME_LIBRARY_STATE));
+ // Process library attributes
+ parseLibraryAttributes(attributes);
+ return;
+ }
+ // If we get to this point, the element name is one we don't currently accept.
+ // Set the state to indicate that this element will be ignored
+ stateStack.push(new Integer(IGNORED_ELEMENT_STATE));
+ internalError(elementName);
+ }
+
+ private void logStatus(SAXParseException ex) {
+ String name = ex.getSystemId();
+ if (name == null)
+ name = ""; //$NON-NLS-1$
+ else
+ name = name.substring(1 + name.lastIndexOf("/")); //$NON-NLS-1$
+ String msg;
+ if (name.equals("")) //$NON-NLS-1$
+ msg = NLS.bind(EclipseAdaptorMsg.parse_error, ex.getMessage());
+ else
+ msg = NLS.bind(EclipseAdaptorMsg.parse_errorNameLineColumn, new String[] {name, Integer.toString(ex.getLineNumber()), Integer.toString(ex.getColumnNumber()), ex.getMessage()});
+
+ FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, msg, 0, ex, null);
+ adaptor.getFrameworkLog().log(entry);
+ }
+
+ synchronized public PluginInfo parsePlugin(InputStream in) throws Exception {
+ SAXParserFactory factory = acquireXMLParsing(context);
+ if (factory == null) {
+ FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, EclipseAdaptorMsg.ECLIPSE_CONVERTER_NO_SAX_FACTORY, 0, null, null);
+ adaptor.getFrameworkLog().log(entry);
+ return null;
+ }
+
+ factory.setNamespaceAware(true);
+ factory.setNamespaceAware(true);
+ try {
+ factory.setFeature("http://xml.org/sax/features/string-interning", true); //$NON-NLS-1$
+ } catch (SAXException se) {
+ // ignore; we can still operate without string-interning
+ }
+ factory.setValidating(false);
+ factory.newSAXParser().parse(in, this);
+ return manifestInfo;
+ }
+
+ public static SAXParserFactory acquireXMLParsing(BundleContext context) {
+ if (xmlTracker == null) {
+ xmlTracker = new ServiceTracker<SAXParserFactory, SAXParserFactory>(context, "javax.xml.parsers.SAXParserFactory", null); //$NON-NLS-1$
+ xmlTracker.open();
+ }
+ SAXParserFactory result = xmlTracker.getService();
+ if (result != null)
+ return result;
+ // backup to using jaxp to create a new instance
+ return SAXParserFactory.newInstance();
+ }
+
+ public static void releaseXMLParsing() {
+ if (xmlTracker != null)
+ xmlTracker.close();
+ }
+
+ public void parseFragmentAttributes(Attributes attributes) {
+ // process attributes
+ objectStack.push(manifestInfo);
+ int len = attributes.getLength();
+ for (int i = 0; i < len; i++) {
+ String attrName = attributes.getLocalName(i);
+ String attrValue = attributes.getValue(i).trim();
+ if (attrName.equals(FRAGMENT_ID))
+ manifestInfo.pluginId = attrValue;
+ else if (attrName.equals(FRAGMENT_NAME))
+ manifestInfo.pluginName = attrValue;
+ else if (attrName.equals(FRAGMENT_VERSION))
+ manifestInfo.version = attrValue;
+ else if (attrName.equals(FRAGMENT_PROVIDER))
+ manifestInfo.vendor = attrValue;
+ else if (attrName.equals(FRAGMENT_PLUGIN_ID))
+ manifestInfo.masterPluginId = attrValue;
+ else if (attrName.equals(FRAGMENT_PLUGIN_VERSION))
+ manifestInfo.masterVersion = attrValue;
+ else if (attrName.equals(FRAGMENT_PLUGIN_MATCH))
+ manifestInfo.masterMatch = attrValue;
+ }
+ }
+
+ public void parseLibraryAttributes(Attributes attributes) {
+ // Push a vector to hold the export mask
+ objectStack.push(new ArrayList<String>());
+ String current = attributes.getValue("", LIBRARY_NAME); //$NON-NLS-1$
+ objectStack.push(current);
+ }
+
+ public void parsePluginAttributes(Attributes attributes) {
+ // process attributes
+ objectStack.push(manifestInfo);
+ int len = attributes.getLength();
+ for (int i = 0; i < len; i++) {
+ String attrName = attributes.getLocalName(i);
+ String attrValue = attributes.getValue(i).trim();
+ if (attrName.equals(PLUGIN_ID))
+ manifestInfo.pluginId = attrValue;
+ else if (attrName.equals(PLUGIN_NAME))
+ manifestInfo.pluginName = attrValue;
+ else if (attrName.equals(PLUGIN_VERSION))
+ manifestInfo.version = attrValue;
+ else if (attrName.equals(PLUGIN_VENDOR) || (attrName.equals(PLUGIN_PROVIDER)))
+ manifestInfo.vendor = attrValue;
+ else if (attrName.equals(PLUGIN_CLASS))
+ manifestInfo.pluginClass = attrValue;
+ }
+ }
+
+ public class Prerequisite {
+ String name;
+ String version;
+ boolean optional;
+ boolean export;
+ String match;
+
+ public boolean isExported() {
+ return export;
+ }
+
+ public String getMatch() {
+ return match;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public boolean isOptional() {
+ return optional;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public Prerequisite(String preqName, String prereqVersion, boolean isOtional, boolean isExported, String prereqMatch) {
+ name = preqName;
+ version = prereqVersion;
+ optional = isOtional;
+ export = isExported;
+ match = prereqMatch;
+ }
+
+ public String toString() {
+ return name;
+ }
+
+ public boolean equals(Object prereq) {
+ if (!(prereq instanceof Prerequisite))
+ return false;
+ return name.equals(((Prerequisite) prereq).name);
+ }
+
+ public int hashCode() {
+ return name.hashCode();
+ }
+ }
+
+ public void parsePluginRequiresImport(Attributes attributes) {
+ if (manifestInfo.requires == null) {
+ manifestInfo.requires = new ArrayList<Prerequisite>();
+ // to avoid cycles
+ // if (!manifestInfo.pluginId.equals(PluginConverterImpl.PI_RUNTIME)) //$NON-NLS-1$
+ // manifestInfo.requires.add(new Prerequisite(PluginConverterImpl.PI_RUNTIME, null, false, false, null)); //$NON-NLS-1$
+ }
+ // process attributes
+ String plugin = attributes.getValue("", PLUGIN_REQUIRES_PLUGIN); //$NON-NLS-1$
+ if (plugin == null)
+ return;
+ if (plugin.equals(PluginConverterImpl.PI_BOOT))
+ return;
+ if (plugin.equals(PluginConverterImpl.PI_RUNTIME_COMPATIBILITY))
+ manifestInfo.compatibilityFound = true;
+ String version = attributes.getValue("", PLUGIN_REQUIRES_PLUGIN_VERSION); //$NON-NLS-1$
+ String optional = attributes.getValue("", PLUGIN_REQUIRES_OPTIONAL); //$NON-NLS-1$
+ String export = attributes.getValue("", PLUGIN_REQUIRES_EXPORT); //$NON-NLS-1$
+ String match = attributes.getValue("", PLUGIN_REQUIRES_MATCH); //$NON-NLS-1$
+ manifestInfo.requires.add(new Prerequisite(plugin, version, "true".equalsIgnoreCase(optional) ? true : false, "true".equalsIgnoreCase(export) ? true : false, match)); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ public void parseRequiresAttributes(Attributes attributes) {
+ //Nothing to do.
+ }
+
+ static String replace(String s, String from, String to) {
+ String str = s;
+ int fromLen = from.length();
+ int toLen = to.length();
+ int ix = str.indexOf(from);
+ while (ix != -1) {
+ str = str.substring(0, ix) + to + str.substring(ix + fromLen);
+ ix = str.indexOf(from, ix + toLen);
+ }
+ return str;
+ }
+
+ public void startDocument() {
+ stateStack.push(new Integer(INITIAL_STATE));
+ }
+
+ public void startElement(String uri, String elementName, String qName, Attributes attributes) {
+ switch (stateStack.peek().intValue()) {
+ case INITIAL_STATE :
+ handleInitialState(elementName, attributes);
+ break;
+ case FRAGMENT_STATE :
+ case PLUGIN_STATE :
+ handlePluginState(elementName, attributes);
+ break;
+ case PLUGIN_RUNTIME_STATE :
+ handleRuntimeState(elementName, attributes);
+ break;
+ case PLUGIN_REQUIRES_STATE :
+ handleRequiresState(elementName, attributes);
+ break;
+ case PLUGIN_EXTENSION_POINT_STATE :
+ handleExtensionPointState(elementName, attributes);
+ break;
+ case PLUGIN_EXTENSION_STATE :
+ handleExtensionState(elementName, attributes);
+ break;
+ case RUNTIME_LIBRARY_STATE :
+ handleLibraryState(elementName, attributes);
+ break;
+ case LIBRARY_EXPORT_STATE :
+ handleLibraryExportState(elementName, attributes);
+ break;
+ case PLUGIN_REQUIRES_IMPORT_STATE :
+ handleRequiresImportState(elementName, attributes);
+ break;
+ default :
+ stateStack.push(new Integer(IGNORED_ELEMENT_STATE));
+ }
+ }
+
+ public void warning(SAXParseException ex) {
+ logStatus(ex);
+ }
+
+ private void internalError(String elementName) {
+ FrameworkLogEntry error;
+ String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_CONVERTER_PARSE_UNKNOWNTOP_ELEMENT, elementName);
+ error = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, (manifestInfo.pluginId == null ? message : "Plug-in : " + manifestInfo.pluginId + ", " + message), 0, null, null); //$NON-NLS-1$ //$NON-NLS-2$
+ adaptor.getFrameworkLog().log(error);
+ }
+
+ /**
+ * @throws SAXException
+ */
+ public void processingInstruction(String instructionTarget, String data) throws SAXException {
+ // Since 3.0, a processing instruction of the form <?eclipse version="3.0"?> at
+ // the start of the manifest file is used to indicate the plug-in manifest
+ // schema version in effect. Pre-3.0 (i.e., 2.1) plug-in manifest files do not
+ // have one of these, and this is how we can distinguish the manifest of a
+ // pre-3.0 plug-in from a post-3.0 one (for compatibility tranformations).
+ if (instructionTarget.equalsIgnoreCase("eclipse")) { //$NON-NLS-1$
+ // just the presence of this processing instruction indicates that this
+ // plug-in is at least 3.0
+ manifestInfo.schemaVersion = "3.0"; //$NON-NLS-1$
+ StringTokenizer tokenizer = new StringTokenizer(data, "=\""); //$NON-NLS-1$
+ while (tokenizer.hasMoreTokens()) {
+ String token = tokenizer.nextToken();
+ if (token.equalsIgnoreCase("version")) { //$NON-NLS-1$
+ if (!tokenizer.hasMoreTokens()) {
+ break;
+ }
+ manifestInfo.schemaVersion = tokenizer.nextToken();
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/Semaphore.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/Semaphore.java
new file mode 100644
index 000000000..0ef4bffcf
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/Semaphore.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.runtime.internal.adaptor;
+
+/**
+ * Internal class.
+ */
+public class Semaphore {
+ protected long notifications;
+
+ public Semaphore(int count) {
+ notifications = count;
+ }
+
+ /**
+ * Attempts to acquire this semaphore. Returns only when the semaphore has been acquired.
+ */
+ public synchronized void acquire() {
+ while (true) {
+ if (notifications > 0) {
+ notifications--;
+ return;
+ }
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ //Ignore
+ }
+ }
+ }
+
+ /**
+ * Attempts to acquire this semaphore. Returns true if it was successfully acquired,
+ * and false otherwise.
+ */
+ public synchronized boolean acquire(long delay) {
+ long start = System.currentTimeMillis();
+ long timeLeft = delay;
+ while (true) {
+ if (notifications > 0) {
+ notifications--;
+ return true;
+ }
+ if (timeLeft <= 0)
+ return false;
+ try {
+ wait(timeLeft);
+ } catch (InterruptedException e) {
+ //Ignore
+ }
+ timeLeft = start + delay - System.currentTimeMillis();
+ }
+ }
+
+ public synchronized void release() {
+ notifications++;
+ notifyAll();
+ }
+
+ // for debug only
+ public String toString() {
+ return "Semaphore(" + notifications + ")"; //$NON-NLS-1$ //$NON-NLS-2$
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/URLConverterImpl.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/URLConverterImpl.java
new file mode 100644
index 000000000..c197861e9
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/adaptor/URLConverterImpl.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2009 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.runtime.internal.adaptor;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import org.eclipse.osgi.framework.internal.core.BundleURLConnection;
+import org.eclipse.osgi.service.urlconversion.URLConverter;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * The service implementation that allows bundleresource or bundleentry
+ * URLs to be converted to native file URLs on the local file system.
+ *
+ * <p>Internal class.</p>
+ */
+public class URLConverterImpl implements URLConverter {
+
+ /* (non-Javadoc)
+ * @see org.eclipse.osgi.service.urlconversion.URLConverter#toFileURL(java.net.URL)
+ */
+ public URL toFileURL(URL url) throws IOException {
+ URLConnection connection = url.openConnection();
+ if (connection instanceof BundleURLConnection) {
+ URL result = ((BundleURLConnection) connection).getFileURL();
+ /* If we got a connection then we know the resource exists in
+ * the bundle but if connection.getFileURL returned null then there
+ * was a problem extracting the file to disk. See bug 259241.
+ **/
+ if (result == null)
+ throw new IOException(NLS.bind(EclipseAdaptorMsg.ECLIPSE_PLUGIN_EXTRACTION_PROBLEM, url));
+ return result;
+ }
+ return url;
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.osgi.service.urlconversion.URLConverter#resolve(java.net.URL)
+ */
+ public URL resolve(URL url) throws IOException {
+ URLConnection connection = url.openConnection();
+ if (connection instanceof BundleURLConnection)
+ return ((BundleURLConnection) connection).getLocalURL();
+ return url;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/BundleStats.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/BundleStats.java
new file mode 100644
index 000000000..f021dfc9b
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/BundleStats.java
@@ -0,0 +1,132 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.runtime.internal.stats;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Contains information about activated bundles and acts as the main
+ * entry point for logging bundle activity.
+ */
+
+public class BundleStats {
+ public String symbolicName;
+ public long id;
+ public int activationOrder;
+ private long timestamp; //timeStamp at which this bundle has been activated
+ private boolean duringStartup; // indicate if the bundle has been activated during startup
+ private long startupTime; // the time took by the bundle to startup
+ private long startupMethodTime; // the time took to run the startup method
+
+ // Indicate the position of the activation trace in the file
+ private long traceStart = -1;
+ private long traceEnd = -1;
+
+ //To keep bundle parentage
+ private List<BundleStats> bundlesActivated = new ArrayList<BundleStats>(3); // TODO create lazily
+ private BundleStats activatedBy = null;
+
+ public BundleStats(String name, long id) {
+ this.symbolicName = name;
+ this.id = id;
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ public int getActivationOrder() {
+ return activationOrder;
+ }
+
+ protected void activated(BundleStats info) {
+ bundlesActivated.add(info);
+ }
+
+ public BundleStats getActivatedBy() {
+ return activatedBy;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public String getSymbolicName() {
+ return symbolicName;
+ }
+
+ public long getStartupTime() {
+ return startupTime;
+ }
+
+ public long getStartupMethodTime() {
+ return startupMethodTime;
+ }
+
+ public boolean isStartupBundle() {
+ return duringStartup;
+ }
+
+ public int getClassLoadCount() {
+ if (!StatsManager.MONITOR_CLASSES)
+ return 0;
+ ClassloaderStats loader = ClassloaderStats.getLoader(symbolicName);
+ return loader == null ? 0 : loader.getClassLoadCount();
+ }
+
+ public long getClassLoadTime() {
+ if (!StatsManager.MONITOR_CLASSES)
+ return 0;
+ ClassloaderStats loader = ClassloaderStats.getLoader(symbolicName);
+ return loader == null ? 0 : loader.getClassLoadTime();
+ }
+
+ public List<BundleStats> getBundlesActivated() {
+ return bundlesActivated;
+ }
+
+ public long getTraceStart() {
+ return traceStart;
+ }
+
+ public long getTraceEnd() {
+ return traceEnd;
+ }
+
+ protected void setTimestamp(long value) {
+ timestamp = value;
+ }
+
+ protected void setActivationOrder(int value) {
+ activationOrder = value;
+ }
+
+ protected void setTraceStart(long time) {
+ traceStart = time;
+ }
+
+ protected void setDuringStartup(boolean value) {
+ duringStartup = value;
+ }
+
+ protected void endActivation() {
+ startupTime = System.currentTimeMillis() - timestamp;
+ }
+
+ protected void setTraceEnd(long position) {
+ traceEnd = position;
+ }
+
+ protected void setActivatedBy(BundleStats value) {
+ activatedBy = value;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/ClassStats.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/ClassStats.java
new file mode 100644
index 000000000..092dd156e
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/ClassStats.java
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.runtime.internal.stats;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Maintain statistics about a loaded class.
+ */
+public class ClassStats {
+ private String className; // fully qualified name of this class
+ private ClassloaderStats classloader; // the classloader that loaded this class
+ private int loadOrder = -1;
+
+ private long timestamp; // time at which this class was loaded
+ private long timeLoading; // time to load the class
+ private long timeLoadingOthers = 0; // time spent loading classes which has been triggered by this class
+
+ // parentage of classes loaded
+ private ClassStats loadedBy = null; // a reference to the class that loaded this class
+ private List<ClassStats> loaded = new ArrayList<ClassStats>(2); // a reference to the classes that this class loaded
+
+ private boolean duringStartup; // indicate if the class was loaded during platform startup
+
+ //information to retrieve the stacktrace from the file
+ private long traceStart = -1;
+ private long traceEnd = -1;
+
+ public ClassStats(String name, ClassloaderStats classloader) {
+ className = name;
+ timestamp = System.currentTimeMillis();
+ duringStartup = StatsManager.isBooting();
+ this.classloader = classloader;
+ }
+
+ public void setLoadOrder(int order) {
+ loadOrder = order;
+ }
+
+ public void loadingDone() {
+ timeLoading = System.currentTimeMillis() - timestamp;
+ }
+
+ public long getTimeLoading() {
+ return timeLoading;
+ }
+
+ public long getLocalTimeLoading() {
+ return timeLoading - timeLoadingOthers;
+ }
+
+ public void addTimeLoadingOthers(long time) {
+ timeLoadingOthers = timeLoadingOthers + time;
+ }
+
+ public long getTraceStart() {
+ return traceStart;
+ }
+
+ public long getTraceEnd() {
+ return traceEnd;
+ }
+
+ public void setTraceStart(long position) {
+ traceStart = position;
+ }
+
+ public void setTraceEnd(long position) {
+ traceEnd = position;
+ }
+
+ public void loaded(ClassStats child) {
+ loaded.add(child);
+ }
+
+ public void setLoadedBy(ClassStats parent) {
+ loadedBy = parent;
+ }
+
+ public ClassStats getLoadedBy() {
+ return loadedBy;
+ }
+
+ public List<ClassStats> getLoadedClasses() {
+ return loaded;
+ }
+
+ public String getClassName() {
+ return className;
+ }
+
+ public boolean isStartupClass() {
+ return duringStartup;
+ }
+
+ public ClassloaderStats getClassloader() {
+ return classloader;
+ }
+
+ public int getLoadOrder() {
+ return loadOrder;
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ public void toBaseClass() {
+ duringStartup = true;
+ loadOrder = -2;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/ClassloaderStats.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/ClassloaderStats.java
new file mode 100644
index 000000000..f34da3da3
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/ClassloaderStats.java
@@ -0,0 +1,242 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.runtime.internal.stats;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * Contains information about the classes and the bundles loaded by a given classloader. Typically there is one classloader per plugin so at levels above boot, this equates to information about
+ * classes and bundles in a plugin.
+ */
+public class ClassloaderStats {
+ private String id;
+ private long loadingTime; // time spent loading classes
+ /**
+ * classes loaded by the plugin (key: class name, value: ClassStats)
+ */
+ private Map<String, ClassStats> classes = Collections.synchronizedMap(new HashMap<String, ClassStats>(20));
+ private List<ResourceBundleStats> bundles = new ArrayList<ResourceBundleStats>(2); // bundles loaded
+
+ private boolean keepTraces = false; // indicate whether or not the traces of classes loaded are kept
+
+ // filters to indicate which classes we want to keep the traces
+ private static List<String> packageFilters = new ArrayList<String>(4); // filters on a package basis
+ private static Set<String> pluginFilters = new HashSet<String>(5); // filters on a plugin basis
+
+ private static Hashtable<Thread, Stack<ClassStats>> classStacks = new Hashtable<Thread, Stack<ClassStats>>(); // represents the classes that are currently being loaded
+ /**
+ * a dictionary of the classloaderStats (key: pluginId, value: ClassloaderStats)
+ */
+ private static Map<String, ClassloaderStats> loaders = Collections.synchronizedMap(new HashMap<String, ClassloaderStats>(20));
+ public static File traceFile;
+
+ static {
+ if (StatsManager.TRACE_CLASSES || StatsManager.TRACE_BUNDLES)
+ initializeTraceOptions();
+ }
+
+ private static void initializeTraceOptions() {
+ // create the trace file
+ String filename = StatsManager.TRACE_FILENAME;
+ traceFile = new File(filename);
+ traceFile.delete();
+
+ //load the filters
+ if (!StatsManager.TRACE_CLASSES)
+ return;
+ filename = StatsManager.TRACE_FILTERS;
+ if (filename == null || filename.length() == 0)
+ return;
+ try {
+ File filterFile = new File(filename);
+ System.out.print("Runtime tracing elements defined in: " + filterFile.getAbsolutePath() + "..."); //$NON-NLS-1$ //$NON-NLS-2$
+ InputStream input = new FileInputStream(filterFile);
+ System.out.println(" Loaded."); //$NON-NLS-1$
+ Properties filters = new Properties() {
+ private static final long serialVersionUID = 3546359543853365296L;
+
+ public synchronized Object put(Object key, Object value) {
+ addFilters((String) key, (String) value);
+ return null;
+ }
+ };
+ try {
+ filters.load(input);
+ } finally {
+ input.close();
+ }
+ } catch (IOException e) {
+ System.out.println(" No trace filters loaded."); //$NON-NLS-1$
+ }
+ }
+
+ protected static void addFilters(String key, String value) {
+ String[] filters = StatsManager.getArrayFromList(value);
+ if ("plugins".equals(key)) //$NON-NLS-1$
+ pluginFilters.addAll(Arrays.asList(filters));
+ if ("packages".equals(key)) //$NON-NLS-1$
+ packageFilters.addAll(Arrays.asList(filters));
+ }
+
+ public static void startLoadingClass(String id, String className) {
+ findLoader(id).startLoadClass(className);
+ }
+
+ // get and create if does not exist
+ private static ClassloaderStats findLoader(String id) {
+ synchronized (loaders) {
+ ClassloaderStats result = loaders.get(id);
+ if (result == null) {
+ result = new ClassloaderStats(id);
+ loaders.put(id, result);
+ }
+ return result;
+ }
+ }
+
+ public static synchronized Stack<ClassStats> getClassStack() {
+ Stack<ClassStats> result = classStacks.get(Thread.currentThread());
+ if (result == null) {
+ result = new Stack<ClassStats>();
+ classStacks.put(Thread.currentThread(), result);
+ }
+ return result;
+ }
+
+ public static ClassloaderStats[] getLoaders() {
+ //the parameter to toArray is of size zero for thread safety, otherwise this
+ //could return an array with null entries if the map shrinks concurrently
+ return loaders.values().toArray(new ClassloaderStats[0]);
+ }
+
+ public static void endLoadingClass(String id, String className, boolean success) {
+ findLoader(id).endLoadClass(className, success);
+ }
+
+ public static void loadedBundle(String id, ResourceBundleStats info) {
+ findLoader(id).loadedBundle(info);
+ }
+
+ public static ClassloaderStats getLoader(String id) {
+ return loaders.get(id);
+ }
+
+ public ClassloaderStats(String id) {
+ this.id = id;
+ keepTraces = pluginFilters.contains(id);
+ }
+
+ public void addBaseClasses(String[] baseClasses) {
+ for (int i = 0; i < baseClasses.length; i++) {
+ String name = baseClasses[i];
+ if (classes.get(name) == null) {
+ ClassStats value = new ClassStats(name, this);
+ value.toBaseClass();
+ classes.put(name, value);
+ }
+ }
+ }
+
+ private void loadedBundle(ResourceBundleStats bundle) {
+ bundles.add(bundle);
+ }
+
+ public List<ResourceBundleStats> getBundles() {
+ return bundles;
+ }
+
+ private synchronized void startLoadClass(String name) {
+ getClassStack().push(findClass(name));
+ }
+
+ // internal method that return the existing classStats or creates one
+ private ClassStats findClass(String name) {
+ ClassStats result = classes.get(name);
+ return result == null ? new ClassStats(name, this) : result;
+ }
+
+ private synchronized void endLoadClass(String name, boolean success) {
+ ClassStats current = getClassStack().pop();
+ if (!success)
+ return;
+ if (current.getLoadOrder() >= 0)
+ return;
+
+ classes.put(name, current);
+ current.setLoadOrder(classes.size());
+ current.loadingDone();
+ traceLoad(name, current);
+
+ // is there something on the load stack. if so, link them together...
+ Stack<ClassStats> classStack = getClassStack();
+ if (classStack.size() != 0) {
+ // get the time spent loading cli and subtract its load time from the class that requires loading
+ ClassStats previous = classStack.peek();
+ previous.addTimeLoadingOthers(current.getTimeLoading());
+ current.setLoadedBy(previous);
+ previous.loaded(current);
+ } else {
+ loadingTime = loadingTime + current.getTimeLoading();
+ }
+ }
+
+ private void traceLoad(String name, ClassStats target) {
+ // Stack trace code
+ if (!keepTraces) {
+ boolean found = false;
+ for (int i = 0; !found && i < packageFilters.size(); i++)
+ if (name.startsWith(packageFilters.get(i)))
+ found = true;
+ if (!found)
+ return;
+ }
+
+ // Write the stack trace. The position in the file are set to the corresponding classStat object
+ try {
+ target.setTraceStart(traceFile.length());
+ PrintWriter output = new PrintWriter(new FileOutputStream(traceFile.getAbsolutePath(), true));
+ try {
+ output.println("Loading class: " + name); //$NON-NLS-1$
+ output.println("Class loading stack:"); //$NON-NLS-1$
+ output.println("\t" + name); //$NON-NLS-1$
+ Stack<ClassStats> classStack = getClassStack();
+ for (int i = classStack.size() - 1; i >= 0; i--)
+ output.println("\t" + classStack.get(i).getClassName()); //$NON-NLS-1$
+ output.println("Stack trace:"); //$NON-NLS-1$
+ new Throwable().printStackTrace(output);
+ } finally {
+ output.close();
+ }
+ target.setTraceEnd(traceFile.length());
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public int getClassLoadCount() {
+ return classes.size();
+ }
+
+ public long getClassLoadTime() {
+ return loadingTime;
+ }
+
+ public ClassStats[] getClasses() {
+ //the parameter to toArray is of size zero for thread safety, otherwise this
+ //could return an array with null entries if the map shrinks concurrently
+ return classes.values().toArray(new ClassStats[0]);
+ }
+
+ public String getId() {
+ return id;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/ResourceBundleStats.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/ResourceBundleStats.java
new file mode 100644
index 000000000..2c072f91c
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/ResourceBundleStats.java
@@ -0,0 +1,123 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.runtime.internal.stats;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.*;
+
+/**
+ * BundleStats is used to represent information about loaded bundle. A
+ * bundlestats instance represents only one bundle.
+ */
+
+public class ResourceBundleStats {
+ private String pluginId; // the plugin loading this bundle
+ private String fileName; // the filename of the bundle
+ private int keyCount = 0; // number of keys in the bundle
+ private int keySize = 0; // size of the keys in the bundle
+ private int valueSize = 0; // size of the values in the bundle
+ private long hashSize = 0; // size of the hashtable
+ private long fileSize = 0;
+
+ private static int sizeOf(String value) {
+ return 44 + (2 * value.length());
+ }
+
+ private static int sizeOf(Properties value) {
+ return (int) Math.round(44 + (16 + (value.size() * 1.25 * 4)) + (24 * value.size()));
+ }
+
+ public ResourceBundleStats(String pluginId, String fileName, URL input) {
+ this.pluginId = pluginId;
+ this.fileName = fileName;
+ initialize(input);
+ }
+
+ public ResourceBundleStats(String pluginId, String fileName, ResourceBundle bundle) {
+ this.pluginId = pluginId;
+ this.fileName = fileName;
+ initialize(bundle);
+ }
+
+ /**
+ * Compute the size of bundle
+ */
+ private void initialize(ResourceBundle bundle) {
+ for (Enumeration<String> keys = bundle.getKeys(); keys.hasMoreElements();) {
+ String key = keys.nextElement();
+ keySize += sizeOf(key);
+ valueSize += sizeOf(bundle.getString(key));
+ keyCount++;
+ }
+ }
+
+ /**
+ * Compute the size of stream which represents a property file
+ */
+ private void initialize(URL url) {
+ InputStream stream = null;
+ Properties props = new Properties();
+ try {
+ try {
+ stream = url.openStream();
+ fileSize = stream.available();
+ props.load(stream);
+ for (Iterator<Object> iter = props.keySet().iterator(); iter.hasNext();) {
+ String key = (String) iter.next();
+ keySize += sizeOf(key);
+ valueSize += sizeOf(props.getProperty(key));
+ keyCount++;
+ }
+ hashSize = sizeOf(props);
+ } finally {
+ if (stream != null)
+ stream.close();
+ }
+ } catch (IOException e) {
+ // ignore exceptions as they will be handled when the stream
+ // is loaded for real. See callers.
+ }
+ }
+
+ public long getHashSize() {
+ return hashSize;
+ }
+
+ public int getKeyCount() {
+ return keyCount;
+ }
+
+ public String getPluginId() {
+ return pluginId;
+ }
+
+ public int getKeySize() {
+ return keySize;
+ }
+
+ public int getValueSize() {
+ return valueSize;
+ }
+
+ public long getTotalSize() {
+ return keySize + valueSize + hashSize;
+ }
+
+ public String getFileName() {
+ return fileName;
+ }
+
+ public long getFileSize() {
+ return fileSize;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/StatsManager.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/StatsManager.java
new file mode 100644
index 000000000..8356d808e
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/internal/stats/StatsManager.java
@@ -0,0 +1,237 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.runtime.internal.stats;
+
+import java.io.*;
+import java.net.URL;
+import java.util.*;
+import org.eclipse.osgi.baseadaptor.HookConfigurator;
+import org.eclipse.osgi.baseadaptor.HookRegistry;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
+import org.eclipse.osgi.baseadaptor.hooks.ClassLoadingStatsHook;
+import org.eclipse.osgi.baseadaptor.loader.ClasspathEntry;
+import org.eclipse.osgi.baseadaptor.loader.ClasspathManager;
+import org.eclipse.osgi.framework.adaptor.BundleWatcher;
+import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.framework.debug.FrameworkDebugOptions;
+import org.eclipse.osgi.util.ManifestElement;
+import org.osgi.framework.Bundle;
+
+public class StatsManager implements BundleWatcher, HookConfigurator, ClassLoadingStatsHook {
+ // This connect bundles and their info, and so allows to access the info without running through
+ // the bundle registry. This map only contains activated bundles. The key is the bundle Id
+ private Hashtable<Long, BundleStats> bundles = new Hashtable<Long, BundleStats>(20);
+ private Map<Thread, Stack<BundleStats>> activationStacks = new HashMap<Thread, Stack<BundleStats>>(5);
+ private static boolean booting = true; // the state of the platform. This value is changed by the InternalPlatform itself.
+
+ private static StatsManager defaultInstance;
+
+ public static boolean MONITOR_ACTIVATION = false;
+ public static boolean MONITOR_CLASSES = false;
+ public static boolean MONITOR_RESOURCES = false;
+ public static String TRACE_FILENAME = "runtime.traces"; //$NON-NLS-1$
+ public static String TRACE_FILTERS = "trace.properties"; //$NON-NLS-1$
+ public static boolean TRACE_CLASSES = false;
+ public static boolean TRACE_BUNDLES = false;
+ public static final String FRAMEWORK_SYMBOLICNAME = "org.eclipse.osgi"; //$NON-NLS-1$
+
+ //Option names for spies
+ private static final String OPTION_MONITOR_ACTIVATION = FRAMEWORK_SYMBOLICNAME + "/monitor/activation"; //$NON-NLS-1$
+ private static final String OPTION_MONITOR_CLASSES = FRAMEWORK_SYMBOLICNAME + "/monitor/classes"; //$NON-NLS-1$
+ private static final String OPTION_MONITOR_RESOURCES = FRAMEWORK_SYMBOLICNAME + "/monitor/resources"; //$NON-NLS-1$
+ private static final String OPTION_TRACE_BUNDLES = FRAMEWORK_SYMBOLICNAME + "/trace/activation"; //$NON-NLS-1$
+ private static final String OPTION_TRACE_CLASSES = FRAMEWORK_SYMBOLICNAME + "/trace/classLoading"; //$NON-NLS-1$
+ private static final String OPTION_TRACE_FILENAME = FRAMEWORK_SYMBOLICNAME + "/trace/filename"; //$NON-NLS-1$
+ private static final String OPTION_TRACE_FILTERS = FRAMEWORK_SYMBOLICNAME + "/trace/filters"; //$NON-NLS-1$
+
+ static {
+ setDebugOptions();
+ }
+
+ public static StatsManager getDefault() {
+ if (defaultInstance == null) {
+ defaultInstance = new StatsManager();
+ defaultInstance.initialize();
+ }
+ return defaultInstance;
+ }
+
+ public static void setDebugOptions() {
+ FrameworkDebugOptions options = FrameworkDebugOptions.getDefault();
+ // may be null if debugging is not enabled
+ if (options == null)
+ return;
+ MONITOR_ACTIVATION = options.getBooleanOption(OPTION_MONITOR_ACTIVATION, false);
+ MONITOR_CLASSES = options.getBooleanOption(OPTION_MONITOR_CLASSES, false);
+ MONITOR_RESOURCES = options.getBooleanOption(OPTION_MONITOR_RESOURCES, false);
+ TRACE_CLASSES = options.getBooleanOption(OPTION_TRACE_CLASSES, false);
+ TRACE_BUNDLES = options.getBooleanOption(OPTION_TRACE_BUNDLES, false);
+ TRACE_FILENAME = options.getOption(OPTION_TRACE_FILENAME, TRACE_FILENAME);
+ TRACE_FILTERS = options.getOption(OPTION_TRACE_FILTERS, TRACE_FILTERS);
+ }
+
+ public static void doneBooting() {
+ booting = false;
+ }
+
+ public static boolean isBooting() {
+ return booting;
+ }
+
+ /**
+ * Returns the result of converting a list of comma-separated tokens into an array
+ *
+ * @return the array of string tokens
+ * @param prop the initial comma-separated string
+ */
+ public static String[] getArrayFromList(String prop) {
+ return ManifestElement.getArrayFromList(prop, ","); //$NON-NLS-1$
+ }
+
+ private void initialize() {
+ // add the system bundle
+ BundleStats bundle = findBundle(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, 0);
+ bundle.setTimestamp(System.currentTimeMillis());
+ bundle.setActivationOrder(bundles.size());
+ bundle.setDuringStartup(booting);
+ }
+
+ public void watchBundle(Bundle bundle, int type) {
+ switch (type) {
+ case BundleWatcher.START_ACTIVATION :
+ startActivation(bundle);
+ break;
+ case BundleWatcher.END_ACTIVATION :
+ endActivation(bundle);
+ break;
+ }
+ }
+
+ public void startActivation(Bundle bundle) {
+ // should be called from a synchronized location to protect against concurrent updates
+ BundleStats info = findBundle(bundle.getSymbolicName(), bundle.getBundleId());
+ info.setTimestamp(System.currentTimeMillis());
+ info.setActivationOrder(bundles.size());
+ info.setDuringStartup(booting);
+
+ Stack<BundleStats> activationStack = activationStacks.get(Thread.currentThread());
+ if (activationStack == null) {
+ activationStack = new Stack<BundleStats>();
+ activationStacks.put(Thread.currentThread(), activationStack);
+ }
+
+ // set the parentage of activation
+ if (activationStack.size() != 0) {
+ BundleStats activatedBy = activationStack.peek();
+ activatedBy.activated(info);
+ info.setActivatedBy(activatedBy);
+ }
+ activationStack.push(info);
+
+ if (TRACE_BUNDLES == true) {
+ traceActivate(bundle, info);
+ }
+ }
+
+ public void endActivation(Bundle symbolicName) {
+ Stack<BundleStats> activationStack = activationStacks.get(Thread.currentThread());
+ // should be called from a synchronized location to protect against concurrent updates
+ BundleStats info = activationStack.pop();
+ info.endActivation();
+ }
+
+ private void traceActivate(Bundle bundle, BundleStats info) {
+ try {
+ PrintWriter output = new PrintWriter(new FileOutputStream(ClassloaderStats.traceFile.getAbsolutePath(), true));
+ try {
+ long startPosition = ClassloaderStats.traceFile.length();
+ output.println("Activating bundle: " + bundle.getSymbolicName()); //$NON-NLS-1$
+ output.println("Bundle activation stack:"); //$NON-NLS-1$
+ Stack<BundleStats> activationStack = activationStacks.get(Thread.currentThread());
+ for (int i = activationStack.size() - 1; i >= 0; i--)
+ output.println("\t" + activationStack.get(i).getSymbolicName()); //$NON-NLS-1$
+ output.println("Class loading stack:"); //$NON-NLS-1$
+ Stack<ClassStats> classStack = ClassloaderStats.getClassStack();
+ for (int i = classStack.size() - 1; i >= 0; i--)
+ output.println("\t" + classStack.get(i).getClassName()); //$NON-NLS-1$
+ output.println("Stack trace:"); //$NON-NLS-1$
+ new Throwable().printStackTrace(output);
+ info.setTraceStart(startPosition);
+ } finally {
+ output.close();
+ info.setTraceEnd(ClassloaderStats.traceFile.length());
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public BundleStats findBundle(String symbolicName, long id) {
+ BundleStats result = bundles.get(new Long(id));
+ try {
+ if (result == null) {
+ result = new BundleStats(symbolicName, id);
+ bundles.put(new Long(id), result);
+ }
+ } catch (IllegalAccessError e) {
+ e.printStackTrace();
+ }
+ return result;
+ }
+
+ public BundleStats[] getBundles() {
+ return bundles.values().toArray(new BundleStats[bundles.size()]);
+ }
+
+ public BundleStats getBundle(long id) {
+ return bundles.get(new Long(id));
+ }
+
+ /**
+ * @throws ClassNotFoundException
+ */
+ public void preFindLocalClass(String name, ClasspathManager manager) throws ClassNotFoundException {
+ if (StatsManager.MONITOR_CLASSES) //Support for performance analysis
+ ClassloaderStats.startLoadingClass(getClassloaderId(manager), name);
+ }
+
+ public void postFindLocalClass(String name, Class<?> clazz, ClasspathManager manager) {
+ if (StatsManager.MONITOR_CLASSES)
+ ClassloaderStats.endLoadingClass(getClassloaderId(manager), name, clazz != null);
+ }
+
+ public void preFindLocalResource(String name, ClasspathManager manager) {
+ // do nothing
+ }
+
+ public void postFindLocalResource(String name, URL resource, ClasspathManager manager) {
+ if (StatsManager.MONITOR_RESOURCES)
+ if (resource != null && name.endsWith(".properties")) //$NON-NLS-1$
+ ClassloaderStats.loadedBundle(getClassloaderId(manager), new ResourceBundleStats(getClassloaderId(manager), name, resource));
+ return;
+ }
+
+ public void recordClassDefine(String name, Class<?> clazz, byte[] classbytes, ClasspathEntry classpathEntry, BundleEntry entry, ClasspathManager manager) {
+ // do nothing
+ }
+
+ private String getClassloaderId(ClasspathManager loader) {
+ return loader.getBaseData().getSymbolicName();
+ }
+
+ public void addHooks(HookRegistry hookRegistry) {
+ if (Debug.MONITOR_ACTIVATION)
+ hookRegistry.addWatcher(StatsManager.getDefault());
+ if (StatsManager.MONITOR_CLASSES || StatsManager.MONITOR_RESOURCES)
+ hookRegistry.addClassLoadingStatsHook(StatsManager.getDefault());
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/BasicReadWriteLock.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/BasicReadWriteLock.java
new file mode 100644
index 000000000..621d31f9b
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/BasicReadWriteLock.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2011 IBM Corporation and others
+ * All rights reserved. This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ ******************************************************************************/
+package org.eclipse.equinox.log.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class BasicReadWriteLock {
+ private List<Thread> currentReaders = new ArrayList<Thread>(2);
+ private int writersWaiting = 0;
+ private Thread writing = null;
+
+ public synchronized int readLock() {
+ while (writing != null || writersWaiting != 0) {
+ try {
+ if (writing == Thread.currentThread())
+ throw new IllegalStateException("Attempted to nest read lock inside a write lock"); //$NON-NLS-1$
+ wait();
+ } catch (InterruptedException e) {
+ // reset interrupted state but keep waiting
+ Thread.currentThread().interrupt();
+ }
+ }
+ currentReaders.add(Thread.currentThread());
+ if (currentReaders.size() == 1)
+ return 1;
+ Thread current = Thread.currentThread();
+ int result = 0;
+ for (Thread reader : currentReaders) {
+ if (reader == current)
+ result++;
+ }
+ return result;
+ }
+
+ public synchronized void readUnlock() {
+ currentReaders.remove(Thread.currentThread());
+ notifyAll();
+ }
+
+ public synchronized void writeLock() {
+ writersWaiting++;
+ try {
+ while (writing != null || currentReaders.size() != 0) {
+ try {
+ if (writing == Thread.currentThread() || currentReaders.contains(Thread.currentThread()))
+ throw new IllegalStateException("Attempted to nest write lock inside a read or write lock"); //$NON-NLS-1$
+ wait();
+ } catch (InterruptedException e) {
+ // reset interrupted state but keep waiting
+ Thread.currentThread().interrupt();
+ }
+ }
+ } finally {
+ writersWaiting--;
+ }
+ writing = Thread.currentThread();
+ }
+
+ public synchronized void writeUnlock() {
+ writing = null;
+ notifyAll();
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/EventAdminAdapter.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/EventAdminAdapter.java
new file mode 100644
index 000000000..66a92b11e
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/EventAdminAdapter.java
@@ -0,0 +1,117 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.equinox.log.internal;
+
+import java.util.*;
+import org.osgi.framework.*;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+
+public class EventAdminAdapter implements ServiceTrackerCustomizer<Object, Object> {
+ public static final String EVENT_TOPIC = "event.topics"; //$NON-NLS-1$
+ private static final String[] LOG_TOPICS_ARRAY = {"*", "org/*", "org/osgi/*", "org/osgi/service/*", "org/osgi/service/log/*", "org/osgi/service/log/LogEntry/*", "org/osgi/service/log/LogEntry/LOG_ERROR", "org/osgi/service/log/LogEntry/LOG_WARNING", "org/osgi/service/log/LogEntry/LOG_INFO", "org/osgi/service/log/LogEntry/LOG_DEBUG", "org/osgi/service/log/LogEntry/LOG_OTHER"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$//$NON-NLS-10$ //$NON-NLS-11$
+ private static final Object LOG_TOPIC_TOKEN = new Object();
+ private static Collection<String> logTopics = new HashSet<String>(Arrays.asList(LOG_TOPICS_ARRAY));
+ private static Collection<String> eventAdminObjectClass = Arrays.asList("org.osgi.service.event.EventAdmin"); //$NON-NLS-1$
+ private static Collection<String> eventHandlerObjectClass = Arrays.asList("org.osgi.service.event.EventHandler"); //$NON-NLS-1$
+
+ private ServiceTracker<Object, Object> eventAdminTracker;
+ private ServiceTracker<Object, Object> eventHandlerTracker;
+ private BundleContext context;
+ private ServiceReference<Object> eventAdmin;
+ private int logEventHandlers;
+ private ExtendedLogReaderServiceFactory logReaderServiceFactory;
+ private EventAdminLogListener logListener;
+
+ public EventAdminAdapter(BundleContext context, ExtendedLogReaderServiceFactory logReaderServiceFactory) {
+ this.context = context;
+ this.logReaderServiceFactory = logReaderServiceFactory;
+ eventAdminTracker = new ServiceTracker<Object, Object>(context, "org.osgi.service.event.EventAdmin", this);
+ eventHandlerTracker = new ServiceTracker<Object, Object>(context, "org.osgi.service.event.EventHandler", this);
+ }
+
+ public void start() {
+ eventAdminTracker.open();
+ eventHandlerTracker.open();
+ }
+
+ public void stop() {
+ eventAdminTracker.close();
+ eventHandlerTracker.close();
+ }
+
+ public Object addingService(ServiceReference<Object> reference) {
+ Object toTrack = null;
+ Object objectClass = reference.getProperty(Constants.OBJECTCLASS);
+ Object topics = reference.getProperty(EVENT_TOPIC);
+ if (checkServiceProp(objectClass, eventAdminObjectClass) && eventAdmin == null) {
+ toTrack = reference;
+ eventAdmin = reference;
+ } else if (checkServiceProp(objectClass, eventHandlerObjectClass) && checkServiceProp(topics, logTopics)) {
+ logEventHandlers++;
+ toTrack = LOG_TOPIC_TOKEN;
+ }
+
+ if (eventAdmin != null && logEventHandlers > 0 && logListener == null) {
+ try {
+ logListener = new EventAdminLogListener(context.getService(eventAdmin));
+ } catch (ClassNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (NoSuchMethodException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ logReaderServiceFactory.addLogListener(logListener, ExtendedLogReaderServiceFactory.NULL_LOGGER_FILTER);
+ }
+
+ return toTrack;
+ }
+
+ public void modifiedService(ServiceReference<Object> reference, Object tracked) {
+ removedService(reference, tracked);
+ addingService(reference);
+ }
+
+ public void removedService(ServiceReference<Object> reference, Object tracked) {
+ if (tracked == eventAdmin) {
+ eventAdmin = null;
+ context.ungetService(reference);
+ } else if (LOG_TOPIC_TOKEN == tracked) {
+ logEventHandlers--;
+ }
+
+ if (logListener != null && (eventAdmin == null || logEventHandlers == 0)) {
+ logReaderServiceFactory.removeLogListener(logListener);
+ logListener = null;
+ }
+ }
+
+ private static boolean checkServiceProp(Object property, Collection<String> check) {
+ if (property instanceof String)
+ return check.contains(property);
+
+ if (property instanceof String[]) {
+ String[] topics = (String[]) property;
+ for (int i = 0; i < topics.length; i++) {
+ if (check.contains(topics[i]))
+ return true;
+ }
+ }
+
+ if (property instanceof Collection) {
+ for (Object prop : (Collection<?>) property)
+ if (check.contains(prop))
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/EventAdminLogListener.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/EventAdminLogListener.java
new file mode 100644
index 000000000..02877e4e1
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/EventAdminLogListener.java
@@ -0,0 +1,153 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2011 IBM Corporation
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+package org.eclipse.equinox.log.internal;
+
+import java.lang.reflect.*;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import org.eclipse.equinox.log.SynchronousLogListener;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.LogEntry;
+import org.osgi.service.log.LogService;
+
+public class EventAdminLogListener implements SynchronousLogListener {
+
+ // constants for Event topic substring
+ public static final String TOPIC = "org/osgi/service/log/LogEntry"; //$NON-NLS-1$
+ public static final char TOPIC_SEPARATOR = '/';
+ // constants for Event types
+ public static final String LOG_ERROR = "LOG_ERROR"; //$NON-NLS-1$
+ public static final String LOG_WARNING = "LOG_WARNING"; //$NON-NLS-1$
+ public static final String LOG_INFO = "LOG_INFO"; //$NON-NLS-1$
+ public static final String LOG_DEBUG = "LOG_DEBUG"; //$NON-NLS-1$
+ public static final String LOG_OTHER = "LOG_OTHER"; //$NON-NLS-1$
+ // constants for Event properties
+ public static final String TIMESTAMP = "timestamp"; //$NON-NLS-1$
+ public static final String MESSAGE = "message"; //$NON-NLS-1$
+ public static final String LOG_LEVEL = "log.level"; //$NON-NLS-1$
+ public static final String LOG_ENTRY = "log.entry"; //$NON-NLS-1$
+ public static final String SERVICE = "service"; //$NON-NLS-1$
+ public static final String SERVICE_ID = "service.id"; //$NON-NLS-1$
+ public static final String SERVICE_OBJECTCLASS = "service.objectClass"; //$NON-NLS-1$
+ public static final String SERVICE_PID = "service.pid"; //$NON-NLS-1$
+ public static final String BUNDLE = "bundle"; //$NON-NLS-1$
+ public static final String BUNDLE_ID = "bundle.id"; //$NON-NLS-1$
+ public static final String BUNDLE_SYMBOLICNAME = "bundle.symbolicName"; //$NON-NLS-1$
+ public static final String EVENT = "event"; //$NON-NLS-1$
+ public static final String EXCEPTION = "exception"; //$NON-NLS-1$
+ public static final String EXCEPTION_CLASS = "exception.class"; //$NON-NLS-1$
+ public static final String EXCEPTION_MESSAGE = "exception.message"; //$NON-NLS-1$
+
+ private final Object eventAdmin;
+ private final Method postEvent;
+ private final Constructor<?> event;
+
+ public EventAdminLogListener(Object eventAdmin) throws ClassNotFoundException, NoSuchMethodException {
+ this.eventAdmin = eventAdmin;
+ Class<?> eventAdminClass = eventAdmin.getClass();
+ ClassLoader cl = eventAdminClass.getClassLoader();
+ Class<?> eventClass = cl.loadClass("org.osgi.service.event.Event"); //$NON-NLS-1$
+
+ postEvent = eventAdminClass.getMethod("postEvent", eventClass); //$NON-NLS-1$
+ event = eventClass.getConstructor(String.class, Dictionary.class);
+ }
+
+ public void logged(LogEntry entry) {
+ try {
+ Object convertedEvent = convertEvent(entry);
+ postEvent.invoke(eventAdmin, convertedEvent);
+ } catch (InvocationTargetException e) {
+ Throwable t = e.getTargetException();
+ if ((t instanceof RuntimeException))
+ throw (RuntimeException) t;
+ if ((t instanceof Error))
+ throw (Error) t;
+ // unexpected
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ // unexpected
+ throw new RuntimeException(e);
+ } catch (InstantiationException e) {
+ // unexpected
+ throw new RuntimeException(e);
+ }
+ }
+
+ private Object convertEvent(LogEntry entry) throws InstantiationException, IllegalAccessException, InvocationTargetException {
+ String topic = TOPIC;
+ int level = entry.getLevel();
+ switch (level) {
+ case LogService.LOG_ERROR :
+ topic += TOPIC_SEPARATOR + LOG_ERROR;
+ break;
+ case LogService.LOG_WARNING :
+ topic += TOPIC_SEPARATOR + LOG_WARNING;
+ break;
+ case LogService.LOG_INFO :
+ topic += TOPIC_SEPARATOR + LOG_INFO;
+ break;
+ case LogService.LOG_DEBUG :
+ topic += TOPIC_SEPARATOR + LOG_DEBUG;
+ break;
+ default : // other log levels are represented by LOG_OTHER
+ topic += TOPIC_SEPARATOR + LOG_OTHER;
+ }
+ Hashtable<String, Object> properties = new Hashtable<String, Object>();
+ Bundle bundle = entry.getBundle();
+ if (bundle == null) {
+ throw new RuntimeException("LogEntry.getBundle() returns null"); //$NON-NLS-1$
+ }
+ putBundleProperties(properties, bundle);
+ Throwable t = entry.getException();
+ if (t != null) {
+ putExceptionProperties(properties, t);
+ }
+ ServiceReference<?> ref = entry.getServiceReference();
+ if (ref != null) {
+ putServiceReferenceProperties(properties, ref);
+ }
+ properties.put(LOG_ENTRY, entry);
+ properties.put(LOG_LEVEL, new Integer(entry.getLevel()));
+ if (entry.getMessage() != null)
+ properties.put(MESSAGE, entry.getMessage());
+ properties.put(TIMESTAMP, new Long(entry.getTime()));
+ return event.newInstance(topic, properties);
+ }
+
+ public static void putServiceReferenceProperties(Hashtable<String, Object> properties, ServiceReference<?> ref) {
+ properties.put(SERVICE, ref);
+ properties.put(SERVICE_ID, ref.getProperty(org.osgi.framework.Constants.SERVICE_ID));
+ Object o = ref.getProperty(org.osgi.framework.Constants.SERVICE_PID);
+ if ((o != null) && (o instanceof String)) {
+ properties.put(SERVICE_PID, o);
+ }
+ Object o2 = ref.getProperty(org.osgi.framework.Constants.OBJECTCLASS);
+ if ((o2 != null) && (o2 instanceof String[])) {
+ properties.put(SERVICE_OBJECTCLASS, o2);
+ }
+ }
+
+ public static void putBundleProperties(Hashtable<String, Object> properties, Bundle bundle) {
+ properties.put(BUNDLE_ID, new Long(bundle.getBundleId()));
+ String symbolicName = bundle.getSymbolicName();
+ if (symbolicName != null) {
+ properties.put(BUNDLE_SYMBOLICNAME, symbolicName);
+ }
+ properties.put(BUNDLE, bundle);
+ }
+
+ public static void putExceptionProperties(Hashtable<String, Object> properties, Throwable t) {
+ properties.put(EXCEPTION, t);
+ properties.put(EXCEPTION_CLASS, t.getClass().getName());
+ String message = t.getMessage();
+ if (message != null) {
+ properties.put(EXCEPTION_MESSAGE, t.getMessage());
+ }
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/ExtendedLogEntryImpl.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/ExtendedLogEntryImpl.java
new file mode 100644
index 000000000..76134e0eb
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/ExtendedLogEntryImpl.java
@@ -0,0 +1,120 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2011 Cognos Incorporated, IBM Corporation and others
+ * All rights reserved. This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ ******************************************************************************/
+package org.eclipse.equinox.log.internal;
+
+import java.util.Map;
+import java.util.WeakHashMap;
+import org.eclipse.equinox.log.ExtendedLogEntry;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.LogEntry;
+
+public class ExtendedLogEntryImpl implements ExtendedLogEntry, LogEntry {
+
+ private static long nextSequenceNumber = 1L;
+ private static long nextThreadId = 1L;
+ private static final Map<Thread, Long> threadIds = createThreadIdMap();
+
+ private final String loggerName;
+ private final Bundle bundle;
+ private final int level;
+ private final String message;
+ private final Throwable throwable;
+ private final Object contextObject;
+ private final long time;
+ private final long threadId;
+ private final String threadName;
+ private final long sequenceNumber;
+
+ private static Map<Thread, Long> createThreadIdMap() {
+ try {
+ Thread.class.getMethod("getId", (Class[]) null); //$NON-NLS-1$
+ } catch (NoSuchMethodException e) {
+ return new WeakHashMap<Thread, Long>();
+ }
+ return null;
+ }
+
+ private static long getId(Thread thread) {
+ if (threadIds == null)
+ return thread.getId();
+
+ Long threadId = threadIds.get(thread);
+ if (threadId == null) {
+ threadId = new Long(nextThreadId++);
+ threadIds.put(thread, threadId);
+ }
+ return threadId.longValue();
+ }
+
+ public ExtendedLogEntryImpl(Bundle bundle, String loggerName, Object contextObject, int level, String message, Throwable throwable) {
+ this.time = System.currentTimeMillis();
+ this.loggerName = loggerName;
+ this.bundle = bundle;
+ this.level = level;
+ this.message = message;
+ this.throwable = throwable;
+ this.contextObject = contextObject;
+
+ Thread currentThread = Thread.currentThread();
+ this.threadName = currentThread.getName();
+
+ synchronized (ExtendedLogEntryImpl.class) {
+ this.threadId = getId(currentThread);
+ this.sequenceNumber = nextSequenceNumber++;
+ }
+ }
+
+ public String getLoggerName() {
+ return loggerName;
+ }
+
+ public long getSequenceNumber() {
+ return sequenceNumber;
+ }
+
+ public long getThreadId() {
+ return threadId;
+ }
+
+ public String getThreadName() {
+ return threadName;
+ }
+
+ public Bundle getBundle() {
+ return bundle;
+ }
+
+ public Throwable getException() {
+ return throwable;
+ }
+
+ public int getLevel() {
+ return level;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ @SuppressWarnings("rawtypes")
+ public ServiceReference getServiceReference() {
+ if (contextObject != null && contextObject instanceof ServiceReference)
+ return (ServiceReference) contextObject;
+
+ return null;
+ }
+
+ public long getTime() {
+ return time;
+ }
+
+ public Object getContext() {
+ return contextObject;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/ExtendedLogReaderServiceFactory.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/ExtendedLogReaderServiceFactory.java
new file mode 100644
index 000000000..f6a038994
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/ExtendedLogReaderServiceFactory.java
@@ -0,0 +1,269 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2012 Cognos Incorporated, IBM Corporation and others
+ * All rights reserved. This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ ******************************************************************************/
+package org.eclipse.equinox.log.internal;
+
+import java.io.PrintStream;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.*;
+import org.eclipse.equinox.log.LogFilter;
+import org.eclipse.equinox.log.SynchronousLogListener;
+import org.eclipse.osgi.internal.baseadaptor.ArrayMap;
+import org.osgi.framework.*;
+import org.osgi.service.log.LogEntry;
+import org.osgi.service.log.LogListener;
+
+public class ExtendedLogReaderServiceFactory implements ServiceFactory<ExtendedLogReaderServiceImpl> {
+
+ static final int MAX_RECURSIONS = 50;
+
+ static final class LogTask implements Runnable {
+ private final LogEntry logEntry;
+ private final LogListener listener;
+
+ LogTask(LogEntry logEntry, LogListener listener) {
+ this.logEntry = logEntry;
+ this.listener = listener;
+ }
+
+ public void run() {
+ safeLogged(listener, logEntry);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static final Enumeration<?> EMPTY_ENUMERATION = Collections.enumeration(Collections.EMPTY_LIST);
+
+ static final LogFilter NULL_LOGGER_FILTER = new LogFilter() {
+ public boolean isLoggable(Bundle b, String loggerName, int logLevel) {
+ return true;
+ }
+ };
+
+ private static final LogFilter[] ALWAYS_LOG = new LogFilter[0];
+
+ private static PrintStream errorStream;
+
+ private final BasicReadWriteLock listenersLock = new BasicReadWriteLock();
+ private ArrayMap<LogListener, Object[]> listeners = new ArrayMap<LogListener, Object[]>(5);
+ private LogFilter[] filters = null;
+ private final ThreadLocal<int[]> nestedCallCount = new ThreadLocal<int[]>();
+
+ static boolean safeIsLoggable(LogFilter filter, Bundle bundle, String name, int level) {
+ try {
+ return filter.isLoggable(bundle, name, level);
+ } catch (RuntimeException e) {
+ // "listener.logged" calls user code and might throw an unchecked exception
+ // we catch the error here to gather information on where the problem occurred.
+ getErrorStream().println("LogFilter.isLoggable threw a non-fatal unchecked exception as follows:"); //$NON-NLS-1$
+ e.printStackTrace(getErrorStream());
+ } catch (LinkageError e) {
+ // Catch linkage errors as these are generally recoverable but let other Errors propagate (see bug 222001)
+ getErrorStream().println("LogFilter.isLoggable threw a non-fatal unchecked exception as follows:"); //$NON-NLS-1$
+ e.printStackTrace(getErrorStream());
+ }
+ return false;
+ }
+
+ private static synchronized PrintStream getErrorStream() {
+ if (errorStream == null)
+ return System.err;
+
+ return errorStream;
+ }
+
+ public static synchronized void setErrorStream(PrintStream ps) {
+ errorStream = ps;
+ }
+
+ static void safeLogged(LogListener listener, LogEntry logEntry) {
+ try {
+ listener.logged(logEntry);
+ } catch (RuntimeException e) {
+ // "listener.logged" calls user code and might throw an unchecked exception
+ // we catch the error here to gather information on where the problem occurred.
+ getErrorStream().println("LogListener.logged threw a non-fatal unchecked exception as follows:"); //$NON-NLS-1$
+ e.printStackTrace(getErrorStream());
+ } catch (LinkageError e) {
+ // Catch linkage errors as these are generally recoverable but let other Errors propagate (see bug 222001)
+ getErrorStream().println("LogListener.logged threw a non-fatal unchecked exception as follows:"); //$NON-NLS-1$
+ e.printStackTrace(getErrorStream());
+ }
+ }
+
+ public ExtendedLogReaderServiceImpl getService(Bundle bundle, ServiceRegistration<ExtendedLogReaderServiceImpl> registration) {
+ return new ExtendedLogReaderServiceImpl(this);
+ }
+
+ public void ungetService(Bundle bundle, ServiceRegistration<ExtendedLogReaderServiceImpl> registration, ExtendedLogReaderServiceImpl service) {
+ service.shutdown();
+ }
+
+ boolean isLoggable(final Bundle bundle, final String name, final int level) {
+ if (System.getSecurityManager() != null) {
+ return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
+ public Boolean run() {
+ return isLoggablePrivileged(bundle, name, level);
+ }
+ });
+ }
+ return isLoggablePrivileged(bundle, name, level);
+ }
+
+ boolean isLoggablePrivileged(Bundle bundle, String name, int level) {
+ LogFilter[] filtersCopy;
+ listenersLock.readLock();
+ try {
+ filtersCopy = filters;
+ } finally {
+ listenersLock.readUnlock();
+ }
+ try {
+ if (incrementNestedCount() == MAX_RECURSIONS)
+ return false;
+ if (filtersCopy == null)
+ return false;
+
+ if (filtersCopy == ALWAYS_LOG)
+ return true;
+
+ int filtersLength = filtersCopy.length;
+ for (int i = 0; i < filtersLength; i++) {
+ LogFilter filter = filtersCopy[i];
+ if (safeIsLoggable(filter, bundle, name, level))
+ return true;
+ }
+ } finally {
+ decrementNestedCount();
+ }
+ return false;
+ }
+
+ private int incrementNestedCount() {
+ int[] count = getCount();
+ count[0] = count[0] + 1;
+ return count[0];
+ }
+
+ private void decrementNestedCount() {
+ int[] count = getCount();
+ if (count[0] == 0)
+ return;
+ count[0] = count[0] - 1;
+ }
+
+ private int[] getCount() {
+ int[] count = nestedCallCount.get();
+ if (count == null) {
+ count = new int[] {0};
+ nestedCallCount.set(count);
+ }
+ return count;
+ }
+
+ void log(final Bundle bundle, final String name, final Object context, final int level, final String message, final Throwable exception) {
+ if (System.getSecurityManager() != null) {
+ AccessController.doPrivileged(new PrivilegedAction<Object>() {
+ public Object run() {
+ logPrivileged(bundle, name, context, level, message, exception);
+ return null;
+ }
+ });
+ } else {
+ logPrivileged(bundle, name, context, level, message, exception);
+ }
+ }
+
+ void logPrivileged(Bundle bundle, String name, Object context, int level, String message, Throwable exception) {
+ LogEntry logEntry = new ExtendedLogEntryImpl(bundle, name, context, level, message, exception);
+ ArrayMap<LogListener, Object[]> listenersCopy;
+ listenersLock.readLock();
+ try {
+ listenersCopy = listeners;
+ } finally {
+ listenersLock.readUnlock();
+ }
+ try {
+ if (incrementNestedCount() >= MAX_RECURSIONS)
+ return;
+ int size = listenersCopy.size();
+ for (int i = 0; i < size; i++) {
+ Object[] listenerObjects = listenersCopy.getValue(i);
+ LogFilter filter = (LogFilter) listenerObjects[0];
+ if (safeIsLoggable(filter, bundle, name, level)) {
+ LogListener listener = listenersCopy.getKey(i);
+ SerializedTaskQueue taskQueue = (SerializedTaskQueue) listenerObjects[1];
+ if (taskQueue != null) {
+ taskQueue.put(new LogTask(logEntry, listener));
+ } else {
+ // log synchronously
+ safeLogged(listener, logEntry);
+ }
+ }
+ }
+ } finally {
+ decrementNestedCount();
+ }
+ }
+
+ void addLogListener(LogListener listener, LogFilter filter) {
+ listenersLock.writeLock();
+ try {
+ ArrayMap<LogListener, Object[]> listenersCopy = new ArrayMap<LogListener, Object[]>(listeners.getKeys(), listeners.getValues());
+ Object[] listenerObjects = listenersCopy.get(listener);
+ if (listenerObjects == null) {
+ // Only create a task queue for non-SynchronousLogListeners
+ SerializedTaskQueue taskQueue = (listener instanceof SynchronousLogListener) ? null : new SerializedTaskQueue(listener.toString());
+ listenerObjects = new Object[] {filter, taskQueue};
+ } else if (filter != listenerObjects[0]) {
+ // update the filter
+ listenerObjects[0] = filter;
+ }
+ listenersCopy.put(listener, listenerObjects);
+ recalculateFilters(listenersCopy);
+ listeners = listenersCopy;
+ } finally {
+ listenersLock.writeUnlock();
+ }
+ }
+
+ private void recalculateFilters(ArrayMap<LogListener, Object[]> listenersCopy) {
+ List<LogFilter> filtersList = new ArrayList<LogFilter>();
+ int size = listenersCopy.size();
+ for (int i = 0; i < size; i++) {
+ Object[] listenerObjects = listenersCopy.getValue(i);
+ LogFilter filter = (LogFilter) listenerObjects[0];
+ if (filter == NULL_LOGGER_FILTER) {
+ filters = ALWAYS_LOG;
+ return;
+ }
+ filtersList.add(filter);
+ }
+
+ if (filtersList.isEmpty())
+ filters = null;
+
+ filters = filtersList.toArray(new LogFilter[filtersList.size()]);
+ }
+
+ void removeLogListener(LogListener listener) {
+ listenersLock.writeLock();
+ try {
+ ArrayMap<LogListener, Object[]> listenersCopy = new ArrayMap<LogListener, Object[]>(listeners.getKeys(), listeners.getValues());
+ listenersCopy.remove(listener);
+ recalculateFilters(listenersCopy);
+ listeners = listenersCopy;
+ } finally {
+ listenersLock.writeUnlock();
+ }
+ }
+
+ Enumeration<?> getLog() {
+ return EMPTY_ENUMERATION;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/ExtendedLogReaderServiceImpl.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/ExtendedLogReaderServiceImpl.java
new file mode 100644
index 000000000..d8567fa43
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/ExtendedLogReaderServiceImpl.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2011 Cognos Incorporated, IBM Corporation and others
+ * All rights reserved. This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ ******************************************************************************/
+package org.eclipse.equinox.log.internal;
+
+import java.util.*;
+import org.eclipse.equinox.log.ExtendedLogReaderService;
+import org.eclipse.equinox.log.LogFilter;
+import org.osgi.service.log.LogListener;
+
+public class ExtendedLogReaderServiceImpl implements ExtendedLogReaderService {
+
+ private final ExtendedLogReaderServiceFactory factory;
+ private Set<LogListener> listeners = new HashSet<LogListener>();
+
+ ExtendedLogReaderServiceImpl(ExtendedLogReaderServiceFactory factory) {
+ this.factory = factory;
+ }
+
+ public synchronized void addLogListener(LogListener listener, LogFilter filter) {
+ checkShutdown();
+ if (listener == null)
+ throw new IllegalArgumentException("LogListener must not be null"); //$NON-NLS-1$
+
+ if (filter == null)
+ throw new IllegalArgumentException("LogFilter must not be null"); //$NON-NLS-1$
+
+ listeners.add(listener);
+ factory.addLogListener(listener, filter);
+ }
+
+ public void addLogListener(LogListener listener) {
+ addLogListener(listener, ExtendedLogReaderServiceFactory.NULL_LOGGER_FILTER);
+ }
+
+ @SuppressWarnings("rawtypes")
+ public Enumeration getLog() {
+ checkShutdown();
+ return factory.getLog();
+ }
+
+ public synchronized void removeLogListener(LogListener listener) {
+ checkShutdown();
+ if (listener == null)
+ throw new IllegalArgumentException("LogListener must not be null"); //$NON-NLS-1$
+
+ factory.removeLogListener(listener);
+ listeners.remove(listener);
+ }
+
+ private synchronized void checkShutdown() {
+ if (listeners == null)
+ throw new IllegalStateException("LogReaderService is shutdown."); //$NON-NLS-1$
+ }
+
+ synchronized void shutdown() {
+ for (LogListener listener : listeners) {
+ factory.removeLogListener(listener);
+ }
+ listeners = null;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/ExtendedLogServiceFactory.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/ExtendedLogServiceFactory.java
new file mode 100644
index 000000000..0ae7ca266
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/ExtendedLogServiceFactory.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2011 Cognos Incorporated, IBM Corporation and others
+ * All rights reserved. This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ ******************************************************************************/
+package org.eclipse.equinox.log.internal;
+
+import java.security.Permission;
+import java.util.HashMap;
+import java.util.Map;
+import org.eclipse.equinox.log.ExtendedLogService;
+import org.eclipse.equinox.log.LogPermission;
+import org.osgi.framework.*;
+
+public class ExtendedLogServiceFactory implements ServiceFactory<ExtendedLogService>, BundleListener {
+
+ private final Permission logPermission = new LogPermission("*", LogPermission.LOG); //$NON-NLS-1$
+ private final ExtendedLogReaderServiceFactory logReaderServiceFactory;
+ private final Map<Bundle, ExtendedLogService> logServices = new HashMap<Bundle, ExtendedLogService>();
+
+ public ExtendedLogServiceFactory(ExtendedLogReaderServiceFactory logReaderServiceFactory) {
+ this.logReaderServiceFactory = logReaderServiceFactory;
+ }
+
+ public ExtendedLogServiceImpl getService(Bundle bundle, ServiceRegistration<ExtendedLogService> registration) {
+ return getLogService(bundle);
+ }
+
+ public void ungetService(Bundle bundle, ServiceRegistration<ExtendedLogService> registration, ExtendedLogService service) {
+ // do nothing
+ // Notice that we do not remove the the LogService impl for the bundle because other bundles
+ // still need to be able to get the cached loggers for a bundle.
+ }
+
+ public void bundleChanged(BundleEvent event) {
+ if (event.getType() == BundleEvent.UNINSTALLED)
+ removeLogService(event.getBundle());
+ }
+
+ synchronized ExtendedLogServiceImpl getLogService(Bundle bundle) {
+ ExtendedLogServiceImpl logService = (ExtendedLogServiceImpl) logServices.get(bundle);
+ if (logService == null) {
+ logService = new ExtendedLogServiceImpl(this, bundle);
+ if (bundle != null && bundle.getState() != Bundle.UNINSTALLED)
+ logServices.put(bundle, logService);
+ }
+ return logService;
+ }
+
+ synchronized void shutdown() {
+ logServices.clear();
+ }
+
+ synchronized void removeLogService(Bundle bundle) {
+ logServices.remove(bundle);
+ }
+
+ boolean isLoggable(Bundle bundle, String name, int level) {
+ return logReaderServiceFactory.isLoggable(bundle, name, level);
+ }
+
+ void log(Bundle bundle, String name, Object context, int level, String message, Throwable exception) {
+ logReaderServiceFactory.log(bundle, name, context, level, message, exception);
+ }
+
+ void checkLogPermission() throws SecurityException {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null)
+ sm.checkPermission(logPermission);
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/ExtendedLogServiceImpl.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/ExtendedLogServiceImpl.java
new file mode 100644
index 000000000..a2760e94d
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/ExtendedLogServiceImpl.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2011 Cognos Incorporated, IBM Corporation and others
+ * All rights reserved. This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ ******************************************************************************/
+package org.eclipse.equinox.log.internal;
+
+import java.util.HashMap;
+import org.eclipse.equinox.log.ExtendedLogService;
+import org.eclipse.equinox.log.Logger;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.LogService;
+
+public class ExtendedLogServiceImpl implements ExtendedLogService, LogService {
+
+ private final ExtendedLogServiceFactory factory;
+ private volatile Bundle bundle;
+ private final HashMap<String, Logger> loggerCache = new HashMap<String, Logger>();
+
+ public ExtendedLogServiceImpl(ExtendedLogServiceFactory factory, Bundle bundle) {
+ this.factory = factory;
+ this.bundle = bundle;
+ }
+
+ public void log(int level, String message) {
+ log(null, level, message, null);
+ }
+
+ public void log(int level, String message, Throwable exception) {
+ log(null, level, message, exception);
+ }
+
+ @SuppressWarnings("rawtypes")
+ public void log(ServiceReference sr, int level, String message) {
+ log(sr, level, message, null);
+ }
+
+ @SuppressWarnings("rawtypes")
+ public void log(ServiceReference sr, int level, String message, Throwable exception) {
+ getLogger(null).log(sr, level, message, exception);
+ }
+
+ public void log(Object context, int level, String message) {
+ log(context, level, message, null);
+ }
+
+ public void log(Object context, int level, String message, Throwable exception) {
+ getLogger(null).log(context, level, message, exception);
+ }
+
+ public synchronized Logger getLogger(String name) {
+ Logger logger = loggerCache.get(name);
+ if (logger == null) {
+ logger = new LoggerImpl(this, name);
+ loggerCache.put(name, logger);
+ }
+ return logger;
+ }
+
+ public Logger getLogger(Bundle logBundle, String name) {
+ if (logBundle == null || logBundle == bundle)
+ return getLogger(name);
+ // only check permission if getting another bundles log
+ factory.checkLogPermission();
+ ExtendedLogService bundleLogService = factory.getLogService(logBundle);
+ return bundleLogService.getLogger(name);
+ }
+
+ public String getName() {
+ return getLogger(null).getName();
+ }
+
+ public boolean isLoggable(int level) {
+ return getLogger(null).isLoggable(level);
+ }
+
+ // package private methods called from Logger
+ boolean isLoggable(String name, int level) {
+ return factory.isLoggable(bundle, name, level);
+ }
+
+ // package private methods called from Logger
+ void log(String name, Object context, int level, String message, Throwable exception) {
+ factory.log(bundle, name, context, level, message, exception);
+ }
+
+ void setBundle(Bundle bundle) {
+ this.bundle = bundle;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/LogServiceManager.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/LogServiceManager.java
new file mode 100644
index 000000000..e9fcc27ec
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/LogServiceManager.java
@@ -0,0 +1,316 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2011 Cognos Incorporated, IBM Corporation and others
+ * All rights reserved. This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ ******************************************************************************/
+package org.eclipse.equinox.log.internal;
+
+import java.io.File;
+import java.io.InputStream;
+import java.net.URL;
+import java.security.cert.X509Certificate;
+import java.util.*;
+import org.eclipse.equinox.log.*;
+import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor;
+import org.osgi.framework.*;
+import org.osgi.service.log.*;
+
+public class LogServiceManager implements BundleListener, FrameworkListener, ServiceListener {
+
+ private static final String[] LOGSERVICE_CLASSES = {LogService.class.getName(), ExtendedLogService.class.getName()};
+ private static final String[] LOGREADERSERVICE_CLASSES = {LogReaderService.class.getName(), ExtendedLogReaderService.class.getName()};
+
+ private ServiceRegistration<?> logReaderServiceRegistration;
+ private ServiceRegistration<?> logServiceRegistration;
+ private final ExtendedLogReaderServiceFactory logReaderServiceFactory = new ExtendedLogReaderServiceFactory();
+ private final ExtendedLogServiceFactory logServiceFactory = new ExtendedLogServiceFactory(logReaderServiceFactory);
+ private final ExtendedLogServiceImpl systemBundleLog = logServiceFactory.getLogService(new MockSystemBundle());
+ private EventAdminAdapter eventAdminAdapter;
+
+ public LogServiceManager(LogListener... systemListeners) {
+ for (LogListener logListener : systemListeners) {
+ if (logListener instanceof LogFilter)
+ logReaderServiceFactory.addLogListener(logListener, (LogFilter) logListener);
+ else
+ logReaderServiceFactory.addLogListener(logListener, ExtendedLogReaderServiceFactory.NULL_LOGGER_FILTER);
+ }
+
+ }
+
+ public void start(BundleContext context) {
+ systemBundleLog.setBundle(context.getBundle());
+ context.addBundleListener(this);
+ context.addServiceListener(this);
+ context.addFrameworkListener(this);
+
+ context.addBundleListener(logServiceFactory);
+ logReaderServiceRegistration = context.registerService(LOGREADERSERVICE_CLASSES, logReaderServiceFactory, null);
+ logServiceRegistration = context.registerService(LOGSERVICE_CLASSES, logServiceFactory, null);
+
+ eventAdminAdapter = new EventAdminAdapter(context, logReaderServiceFactory);
+ eventAdminAdapter.start();
+ }
+
+ public void stop(BundleContext context) {
+ eventAdminAdapter.stop();
+ eventAdminAdapter = null;
+ logServiceRegistration.unregister();
+ logServiceRegistration = null;
+ logReaderServiceRegistration.unregister();
+ logReaderServiceRegistration = null;
+ logServiceFactory.shutdown();
+ context.removeFrameworkListener(this);
+ context.removeServiceListener(this);
+ context.removeBundleListener(this);
+ }
+
+ public ExtendedLogService getSystemBundleLog() {
+ return systemBundleLog;
+ }
+
+ /**
+ * BundleListener.bundleChanged method.
+ *
+ */
+ public void bundleChanged(BundleEvent event) {
+ Bundle bundle = event.getBundle();
+ if (logReaderServiceFactory.isLoggable(bundle, null, LogService.LOG_INFO))
+ logReaderServiceFactory.log(bundle, null, null, LogService.LOG_INFO, getBundleEventTypeName(event.getType()), null);
+ }
+
+ /**
+ * ServiceListener.serviceChanged method.
+ *
+ */
+ public void serviceChanged(ServiceEvent event) {
+ ServiceReference<?> reference = event.getServiceReference();
+ Bundle bundle = reference.getBundle();
+ int eventType = event.getType();
+ int logType = (eventType == ServiceEvent.MODIFIED) ? LogService.LOG_DEBUG : LogService.LOG_INFO;
+ if (logReaderServiceFactory.isLoggable(bundle, null, logType))
+ logReaderServiceFactory.log(bundle, null, reference, logType, getServiceEventTypeName(eventType), null);
+ }
+
+ /**
+ * FrameworkListener.frameworkEvent method.
+ *
+ */
+ public void frameworkEvent(FrameworkEvent event) {
+ Bundle bundle = event.getBundle();
+ int eventType = event.getType();
+ int logType = (eventType == FrameworkEvent.ERROR) ? LogService.LOG_ERROR : LogService.LOG_INFO;
+ Throwable throwable = (eventType == FrameworkEvent.ERROR) ? event.getThrowable() : null;
+ if (logReaderServiceFactory.isLoggable(bundle, null, logType))
+ logReaderServiceFactory.log(bundle, null, null, logType, getFrameworkEventTypeName(eventType), throwable);
+ }
+
+ /**
+ * Convert BundleEvent type to a string.
+ *
+ */
+ private static String getBundleEventTypeName(int type) {
+ switch (type) {
+ case BundleEvent.INSTALLED :
+ return ("BundleEvent INSTALLED"); //$NON-NLS-1$
+
+ case BundleEvent.RESOLVED :
+ return ("BundleEvent RESOLVED"); //$NON-NLS-1$
+
+ case BundleEvent.STARTED :
+ return ("BundleEvent STARTED"); //$NON-NLS-1$
+
+ case BundleEvent.STARTING :
+ return ("BundleEvent STARTING"); //$NON-NLS-1$
+
+ case BundleEvent.STOPPED :
+ return ("BundleEvent STOPPED"); //$NON-NLS-1$
+
+ case BundleEvent.STOPPING :
+ return ("BundleEvent STOPPING"); //$NON-NLS-1$
+
+ case BundleEvent.UNINSTALLED :
+ return ("BundleEvent UNINSTALLED"); //$NON-NLS-1$
+
+ case BundleEvent.UNRESOLVED :
+ return ("BundleEvent UNRESOLVED"); //$NON-NLS-1$
+
+ case BundleEvent.UPDATED :
+ return ("BundleEvent UPDATED"); //$NON-NLS-1$
+
+ default :
+ return ("BundleEvent " + Integer.toHexString(type)); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Convert ServiceEvent type to a string.
+ *
+ */
+ private static String getServiceEventTypeName(int type) {
+ switch (type) {
+ case ServiceEvent.REGISTERED :
+ return ("ServiceEvent REGISTERED"); //$NON-NLS-1$
+
+ case ServiceEvent.MODIFIED :
+ return ("ServiceEvent MODIFIED"); //$NON-NLS-1$
+
+ case ServiceEvent.UNREGISTERING :
+ return ("ServiceEvent UNREGISTERING"); //$NON-NLS-1$
+
+ default :
+ return ("ServiceEvent " + Integer.toHexString(type)); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Convert FrameworkEvent type to a string.
+ *
+ */
+ private static String getFrameworkEventTypeName(int type) {
+ switch (type) {
+ case FrameworkEvent.ERROR :
+ return ("FrameworkEvent ERROR"); //$NON-NLS-1$
+
+ case FrameworkEvent.INFO :
+ return ("FrameworkEvent INFO"); //$NON-NLS-1$
+
+ case FrameworkEvent.PACKAGES_REFRESHED :
+ return ("FrameworkEvent PACKAGES REFRESHED"); //$NON-NLS-1$
+
+ case FrameworkEvent.STARTED :
+ return ("FrameworkEvent STARTED"); //$NON-NLS-1$
+
+ case FrameworkEvent.STARTLEVEL_CHANGED :
+ return ("FrameworkEvent STARTLEVEL CHANGED"); //$NON-NLS-1$
+
+ case FrameworkEvent.WARNING :
+ return ("FrameworkEvent WARNING"); //$NON-NLS-1$
+
+ default :
+ return ("FrameworkEvent " + Integer.toHexString(type)); //$NON-NLS-1$
+ }
+ }
+
+ static class MockSystemBundle implements Bundle {
+
+ public int compareTo(Bundle o) {
+ long idcomp = getBundleId() - o.getBundleId();
+ return (idcomp < 0L) ? -1 : ((idcomp > 0L) ? 1 : 0);
+ }
+
+ public int getState() {
+ return Bundle.RESOLVED;
+ }
+
+ public void start(int options) {
+ // nothing
+ }
+
+ public void start() {
+ // nothing
+ }
+
+ public void stop(int options) {
+ // nothing
+ }
+
+ public void stop() {
+ // nothing
+ }
+
+ public void update(InputStream input) {
+ // nothing
+ }
+
+ public void update() {
+ // nothing
+ }
+
+ public void uninstall() {
+ // nothing
+ }
+
+ public Dictionary<String, String> getHeaders() {
+ return new Hashtable<String, String>();
+ }
+
+ public long getBundleId() {
+ return 0;
+ }
+
+ public String getLocation() {
+ return Constants.SYSTEM_BUNDLE_LOCATION;
+ }
+
+ public ServiceReference<?>[] getRegisteredServices() {
+ return null;
+ }
+
+ public ServiceReference<?>[] getServicesInUse() {
+ return null;
+ }
+
+ public boolean hasPermission(Object permission) {
+ return true;
+ }
+
+ public URL getResource(String name) {
+ return null;
+ }
+
+ public Dictionary<String, String> getHeaders(String locale) {
+ return null;
+ }
+
+ public String getSymbolicName() {
+ return FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME;
+ }
+
+ public Class<?> loadClass(String name) throws ClassNotFoundException {
+ throw new ClassNotFoundException();
+ }
+
+ public Enumeration<URL> getResources(String name) {
+ return null;
+ }
+
+ public Enumeration<String> getEntryPaths(String path) {
+ return null;
+ }
+
+ public URL getEntry(String path) {
+ return null;
+ }
+
+ public long getLastModified() {
+ return System.currentTimeMillis();
+ }
+
+ public Enumeration<URL> findEntries(String path, String filePattern, boolean recurse) {
+ return null;
+ }
+
+ public BundleContext getBundleContext() {
+ return null;
+ }
+
+ public Map<X509Certificate, List<X509Certificate>> getSignerCertificates(int signersType) {
+ return new HashMap<X509Certificate, List<X509Certificate>>();
+ }
+
+ public Version getVersion() {
+ return new Version(0, 0, 0);
+ }
+
+ public <A> A adapt(Class<A> type) {
+ return null;
+ }
+
+ public File getDataFile(String filename) {
+ return null;
+ }
+
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/LoggerImpl.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/LoggerImpl.java
new file mode 100644
index 000000000..0a5559201
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/LoggerImpl.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2011 Cognos Incorporated, IBM Corporation and others
+ * All rights reserved. This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ ******************************************************************************/
+package org.eclipse.equinox.log.internal;
+
+import org.eclipse.equinox.log.Logger;
+import org.osgi.framework.ServiceReference;
+
+public class LoggerImpl implements Logger {
+
+ private final ExtendedLogServiceImpl logServiceImpl;
+ private final String name;
+
+ public LoggerImpl(ExtendedLogServiceImpl logServiceImpl, String name) {
+ this.logServiceImpl = logServiceImpl;
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public boolean isLoggable(int level) {
+ return logServiceImpl.isLoggable(name, level);
+ }
+
+ public void log(int level, String message) {
+ log(null, level, message, null);
+ }
+
+ public void log(int level, String message, Throwable exception) {
+ log(null, level, message, exception);
+ }
+
+ @SuppressWarnings("rawtypes")
+ public void log(ServiceReference sr, int level, String message) {
+ log(sr, level, message, null);
+ }
+
+ @SuppressWarnings("rawtypes")
+ public void log(ServiceReference sr, int level, String message, Throwable exception) {
+ logServiceImpl.log(name, sr, level, message, exception);
+ }
+
+ public void log(Object context, int level, String message) {
+ log(context, level, message, null);
+ }
+
+ public void log(Object context, int level, String message, Throwable exception) {
+ logServiceImpl.log(name, context, level, message, exception);
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/SerializedTaskQueue.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/SerializedTaskQueue.java
new file mode 100644
index 000000000..9c15ad453
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/equinox/log/internal/SerializedTaskQueue.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2011 Cognos Incorporated, IBM Corporation and others
+ * All rights reserved. This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ ******************************************************************************/
+package org.eclipse.equinox.log.internal;
+
+import java.util.LinkedList;
+
+/**
+ * SerializedTaskQueue is a utility class that will allow asynchronous but serialized execution of tasks
+ */
+public class SerializedTaskQueue {
+
+ private static final int MAX_WAIT = 5000;
+ private final LinkedList<Runnable> tasks = new LinkedList<Runnable>();
+ private Thread thread;
+ private final String queueName;
+
+ public SerializedTaskQueue(String queueName) {
+ this.queueName = queueName;
+ }
+
+ public synchronized void put(Runnable newTask) {
+ tasks.add(newTask);
+ if (thread == null) {
+ thread = new Thread(queueName) {
+ public void run() {
+ Runnable task = nextTask(MAX_WAIT);
+ while (task != null) {
+ task.run();
+ task = nextTask(MAX_WAIT);
+ }
+ }
+ };
+ thread.start();
+ } else
+ notify();
+ }
+
+ synchronized Runnable nextTask(int maxWait) {
+ if (tasks.isEmpty()) {
+ try {
+ wait(maxWait);
+ } catch (InterruptedException e) {
+ // ignore -- we control the stack here and do not need to propagate it.
+ }
+
+ if (tasks.isEmpty()) {
+ thread = null;
+ return null;
+ }
+ }
+ return tasks.removeFirst();
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/BaseAdaptor.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/BaseAdaptor.java
new file mode 100644
index 000000000..0c2aebb92
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/BaseAdaptor.java
@@ -0,0 +1,699 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.baseadaptor;
+
+import java.io.*;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.*;
+import org.eclipse.core.runtime.adaptor.LocationManager;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile;
+import org.eclipse.osgi.baseadaptor.hooks.*;
+import org.eclipse.osgi.framework.adaptor.*;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.framework.internal.core.*;
+import org.eclipse.osgi.framework.internal.core.Constants;
+import org.eclipse.osgi.framework.log.FrameworkLog;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+import org.eclipse.osgi.internal.baseadaptor.*;
+import org.eclipse.osgi.service.resolver.PlatformAdmin;
+import org.eclipse.osgi.service.resolver.State;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.*;
+import org.osgi.framework.wiring.BundleWiring;
+
+/**
+ * A Framework adaptor implementation that allows additional functionality to be
+ * hooked in. Hooks are configured using {@link HookConfigurator}
+ * objects. A framework extension may add hook configurators which can be used
+ * to add hooks to the {@link HookRegistry}.
+ * @see HookConfigurator
+ * @see HookRegistry
+ * @see AdaptorHook
+ * @since 3.2
+ */
+public class BaseAdaptor implements FrameworkAdaptor {
+ // System property used to set the parent classloader type (boot is the default)
+ private static final String PROP_PARENT_CLASSLOADER = "osgi.parentClassloader"; //$NON-NLS-1$
+ // A parent classloader type that specifies the application classloader
+ private static final String PARENT_CLASSLOADER_APP = "app"; //$NON-NLS-1$
+ // A parent classloader type that specifies the extension classlaoder
+ private static final String PARENT_CLASSLOADER_EXT = "ext"; //$NON-NLS-1$
+ // A parent classloader type that specifies the boot classlaoder
+ private static final String PARENT_CLASSLOADER_BOOT = "boot"; //$NON-NLS-1$
+ // A parent classloader type that specifies the framework classlaoder
+ private static final String PARENT_CLASSLOADER_FWK = "fwk"; //$NON-NLS-1$
+ // The BundleClassLoader parent to use when creating BundleClassLoaders.
+ private static ClassLoader bundleClassLoaderParent;
+ static {
+ // check property for specified parent
+ // check the osgi defined property first
+ String type = FrameworkProperties.getProperty(Constants.FRAMEWORK_BUNDLE_PARENT);
+ if (type != null) {
+ if (Constants.FRAMEWORK_BUNDLE_PARENT_FRAMEWORK.equals(type))
+ type = PARENT_CLASSLOADER_FWK;
+ } else {
+ type = FrameworkProperties.getProperty(BaseAdaptor.PROP_PARENT_CLASSLOADER, BaseAdaptor.PARENT_CLASSLOADER_BOOT);
+ }
+
+ if (BaseAdaptor.PARENT_CLASSLOADER_FWK.equalsIgnoreCase(type))
+ bundleClassLoaderParent = FrameworkAdaptor.class.getClassLoader();
+ else if (BaseAdaptor.PARENT_CLASSLOADER_APP.equalsIgnoreCase(type))
+ bundleClassLoaderParent = ClassLoader.getSystemClassLoader();
+ else if (BaseAdaptor.PARENT_CLASSLOADER_EXT.equalsIgnoreCase(type)) {
+ ClassLoader appCL = ClassLoader.getSystemClassLoader();
+ if (appCL != null)
+ bundleClassLoaderParent = appCL.getParent();
+ }
+ // default to boot classloader
+ if (bundleClassLoaderParent == null)
+ bundleClassLoaderParent = new ClassLoader(Object.class.getClassLoader()) {/* boot class loader*/};
+ }
+
+ private Framework eventPublisher;
+ private boolean stopping;
+ private HookRegistry hookRegistry;
+ private FrameworkLog log;
+ private BundleContext context;
+ private BaseStorage storage;
+ private BundleWatcher bundleWatcher;
+
+ /**
+ * Constructs a BaseAdaptor.
+ * @param args arguments passed to the adaptor by the framework.
+ */
+ public BaseAdaptor(String[] args) {
+ if (LocationManager.getConfigurationLocation() == null)
+ LocationManager.initializeLocations();
+ hookRegistry = new HookRegistry(this);
+ FrameworkLogEntry[] errors = hookRegistry.initialize();
+ if (errors.length > 0)
+ for (int i = 0; i < errors.length; i++)
+ getFrameworkLog().log(errors[i]);
+ // get the storage after the registry has been initialized
+ storage = getStorage();
+ // TODO consider passing args to BaseAdaptorHooks
+ }
+
+ /**
+ * This method will call all configured adaptor hooks {@link AdaptorHook#initialize(BaseAdaptor)} method.
+ * @see FrameworkAdaptor#initialize(EventPublisher)
+ */
+ public void initialize(EventPublisher publisher) {
+ this.eventPublisher = (Framework) publisher;
+ // set the adaptor for the adaptor hooks
+ AdaptorHook[] adaptorHooks = getHookRegistry().getAdaptorHooks();
+ for (int i = 0; i < adaptorHooks.length; i++)
+ adaptorHooks[i].initialize(this);
+ }
+
+ /**
+ * @see FrameworkAdaptor#initializeStorage()
+ */
+ public void initializeStorage() throws IOException {
+ storage.initialize(this);
+ }
+
+ /**
+ * @throws IOException
+ * @see FrameworkAdaptor#compactStorage()
+ */
+ public void compactStorage() throws IOException {
+ storage.compact();
+ }
+
+ /**
+ * This method will call all the configured adaptor hook {@link AdaptorHook#addProperties(Properties)} methods.
+ * @see FrameworkAdaptor#getProperties()
+ */
+ public Properties getProperties() {
+ Properties props = new Properties();
+ String resource = FrameworkProperties.getProperty(Constants.OSGI_PROPERTIES, Constants.DEFAULT_OSGI_PROPERTIES);
+ try {
+ InputStream in = null;
+ File file = new File(resource);
+ if (file.exists())
+ in = new FileInputStream(file);
+ if (in == null)
+ in = getClass().getResourceAsStream(resource);
+ if (in != null) {
+ try {
+ props.load(new BufferedInputStream(in));
+ } finally {
+ try {
+ in.close();
+ } catch (IOException ee) {
+ // nothing to do
+ }
+ }
+ } else {
+ if (Debug.DEBUG_GENERAL)
+ Debug.println("Skipping osgi.properties: " + resource); //$NON-NLS-1$
+ }
+ } catch (IOException e) {
+ if (Debug.DEBUG_GENERAL)
+ Debug.println("Unable to load osgi.properties: " + e.getMessage()); //$NON-NLS-1$
+ }
+ // add the storage properties
+ storage.addProperties(props);
+ // add the properties from each adaptor hook
+ AdaptorHook[] adaptorHooks = getHookRegistry().getAdaptorHooks();
+ for (int i = 0; i < adaptorHooks.length; i++)
+ adaptorHooks[i].addProperties(props);
+ return props;
+ }
+
+ /**
+ * @see FrameworkAdaptor#getInstalledBundles()
+ */
+ public BundleData[] getInstalledBundles() {
+ return storage.getInstalledBundles();
+ }
+
+ /**
+ * This method will call each configured adaptor hook {@link AdaptorHook#mapLocationToURLConnection(String)} method
+ * until one returns a non-null value. If none of the adaptor hooks return a non-null value then the
+ * string is used to construct a new URL object to open a new url connection.
+ *
+ * @see FrameworkAdaptor#mapLocationToURLConnection(String)
+ */
+ public URLConnection mapLocationToURLConnection(String location) throws BundleException {
+ try {
+ URLConnection result = null;
+ // try the adaptor hooks first;
+ AdaptorHook[] adaptorHooks = getHookRegistry().getAdaptorHooks();
+ for (int i = 0; i < adaptorHooks.length; i++) {
+ result = adaptorHooks[i].mapLocationToURLConnection(location);
+ if (result != null)
+ return result;
+ }
+ // just do the default
+ return (new URL(location).openConnection());
+ } catch (IOException e) {
+ throw new BundleException(NLS.bind(AdaptorMsg.ADAPTOR_URL_CREATE_EXCEPTION, location), e);
+ }
+ }
+
+ /**
+ * @see FrameworkAdaptor#installBundle(String, URLConnection)
+ */
+ public BundleOperation installBundle(String location, URLConnection source) {
+ return storage.installBundle(location, source);
+ }
+
+ /**
+ * @see FrameworkAdaptor#updateBundle(BundleData, URLConnection)
+ */
+ public BundleOperation updateBundle(BundleData bundledata, URLConnection source) {
+ return storage.updateBundle((BaseData) bundledata, source);
+ }
+
+ /**
+ * @see FrameworkAdaptor#uninstallBundle(BundleData)
+ */
+ public BundleOperation uninstallBundle(BundleData bundledata) {
+ return storage.uninstallBundle((BaseData) bundledata);
+ }
+
+ /**
+ * @see FrameworkAdaptor#getTotalFreeSpace()
+ */
+ public long getTotalFreeSpace() throws IOException {
+ return storage.getFreeSpace();
+ }
+
+ /**
+ * @throws IOException
+ * @see FrameworkAdaptor#getPermissionStorage()
+ */
+ public PermissionStorage getPermissionStorage() throws IOException {
+ return storage.getPermissionStorage();
+ }
+
+ /**
+ * This method calls all the configured adaptor hook {@link AdaptorHook#frameworkStart(BundleContext)} methods.
+ * @see FrameworkAdaptor#frameworkStart(BundleContext)
+ */
+ public void frameworkStart(BundleContext fwContext) throws BundleException {
+ this.context = fwContext;
+ stopping = false;
+ // always start the storage first
+ storage.frameworkStart(fwContext);
+ AdaptorHook[] adaptorHooks = getHookRegistry().getAdaptorHooks();
+ for (int i = 0; i < adaptorHooks.length; i++)
+ adaptorHooks[i].frameworkStart(fwContext);
+ }
+
+ /**
+ * This method calls all the configured adaptor hook {@link AdaptorHook#frameworkStop(BundleContext)} methods.
+ * @see FrameworkAdaptor#frameworkStop(BundleContext)
+ */
+ public void frameworkStop(BundleContext fwContext) throws BundleException {
+ // first inform all configured adaptor hooks
+ AdaptorHook[] adaptorHooks = getHookRegistry().getAdaptorHooks();
+ for (int i = 0; i < adaptorHooks.length; i++)
+ adaptorHooks[i].frameworkStop(fwContext);
+ // stop the storage last
+ storage.frameworkStop(fwContext);
+ }
+
+ /**
+ * This method calls all the configured adaptor hook {@link AdaptorHook#frameworkStopping(BundleContext)} methods.
+ * @see FrameworkAdaptor#frameworkStopping(BundleContext)
+ */
+ public void frameworkStopping(BundleContext fwContext) {
+ stopping = true;
+ // always tell storage of stopping first
+ storage.frameworkStopping(fwContext);
+ // inform all configured adaptor hooks last
+ AdaptorHook[] adaptorHooks = getHookRegistry().getAdaptorHooks();
+ for (int i = 0; i < adaptorHooks.length; i++)
+ adaptorHooks[i].frameworkStopping(fwContext);
+ }
+
+ /**
+ * @see FrameworkAdaptor#getInitialBundleStartLevel()
+ */
+ public int getInitialBundleStartLevel() {
+ return storage.getInitialBundleStartLevel();
+ }
+
+ /**
+ * @see FrameworkAdaptor#setInitialBundleStartLevel(int)
+ */
+ public void setInitialBundleStartLevel(int value) {
+ storage.setInitialBundleStartLevel(value);
+ }
+
+ /**
+ * This method calls all configured adaptor hook {@link AdaptorHook#createFrameworkLog()} methods
+ * until the first one returns a non-null value. If none of the adaptor hooks return a non-null
+ * value then a framework log implementation which does nothing is returned.
+ * @see FrameworkAdaptor#getFrameworkLog()
+ */
+ public FrameworkLog getFrameworkLog() {
+ if (log != null)
+ return log;
+ AdaptorHook[] adaptorHooks = getHookRegistry().getAdaptorHooks();
+ for (int i = 0; i < adaptorHooks.length; i++) {
+ log = adaptorHooks[i].createFrameworkLog();
+ if (log != null)
+ return log;
+ }
+ log = new FrameworkLog() {
+ public void log(FrameworkEvent frameworkEvent) {
+ log(new FrameworkLogEntry(frameworkEvent.getBundle().getSymbolicName() == null ? frameworkEvent.getBundle().getLocation() : frameworkEvent.getBundle().getSymbolicName(), FrameworkLogEntry.ERROR, 0, "FrameworkEvent.ERROR", 0, frameworkEvent.getThrowable(), null)); //$NON-NLS-1$
+ }
+
+ public void log(FrameworkLogEntry logEntry) {
+ System.err.print(logEntry.getEntry() + " "); //$NON-NLS-1$
+ System.err.println(logEntry.getMessage());
+ if (logEntry.getThrowable() != null)
+ logEntry.getThrowable().printStackTrace(System.err);
+ }
+
+ public void setWriter(Writer newWriter, boolean append) {
+ // do nothing
+ }
+
+ /**
+ * @throws IOException
+ */
+ public void setFile(File newFile, boolean append) throws IOException {
+ // do nothing
+ }
+
+ public File getFile() {
+ // do nothing
+ return null;
+ }
+
+ public void setConsoleLog(boolean consoleLog) {
+ // do nothing
+ }
+
+ public void close() {
+ // do nothing
+ }
+ };
+ return log;
+ }
+
+ /**
+ * @see FrameworkAdaptor#createSystemBundleData()
+ */
+ public BundleData createSystemBundleData() throws BundleException {
+ return new SystemBundleData(this);
+ }
+
+ /**
+ * @see FrameworkAdaptor#getBundleWatcher()
+ */
+ public BundleWatcher getBundleWatcher() {
+ if (bundleWatcher != null)
+ return bundleWatcher;
+ final BundleWatcher[] watchers = hookRegistry.getWatchers();
+ if (watchers.length == 0)
+ return null;
+ bundleWatcher = new BundleWatcher() {
+ public void watchBundle(Bundle bundle, int type) {
+ for (int i = 0; i < watchers.length; i++)
+ watchers[i].watchBundle(bundle, type);
+ }
+ };
+ return bundleWatcher;
+ }
+
+ /**
+ * @see FrameworkAdaptor#getPlatformAdmin()
+ */
+ public PlatformAdmin getPlatformAdmin() {
+ return storage.getStateManager();
+ }
+
+ /**
+ * @see FrameworkAdaptor#getState()
+ */
+ public State getState() {
+ return storage.getStateManager().getSystemState();
+ }
+
+ /**
+ * This method calls all the configured classloading hooks {@link ClassLoadingHook#getBundleClassLoaderParent()} methods
+ * until one returns a non-null value.
+ * @see FrameworkAdaptor#getBundleClassLoaderParent()
+ */
+ public ClassLoader getBundleClassLoaderParent() {
+ // ask the configured adaptor hooks first
+ ClassLoader result = null;
+ ClassLoadingHook[] cpManagerHooks = getHookRegistry().getClassLoadingHooks();
+ for (int i = 0; i < cpManagerHooks.length; i++) {
+ result = cpManagerHooks[i].getBundleClassLoaderParent();
+ if (result != null)
+ return result;
+ }
+ // none of the configured adaptor hooks gave use a parent loader; use the default
+ return bundleClassLoaderParent;
+ }
+
+ /**
+ * This method calls all the configured adaptor hooks {@link AdaptorHook#handleRuntimeError(Throwable)} methods.
+ * @see FrameworkAdaptor#handleRuntimeError(Throwable)
+ */
+ public void handleRuntimeError(Throwable error) {
+ AdaptorHook[] adaptorHooks = getHookRegistry().getAdaptorHooks();
+ for (int i = 0; i < adaptorHooks.length; i++)
+ adaptorHooks[i].handleRuntimeError(error);
+ }
+
+ /**
+ * Returns true if the {@link #frameworkStopping(BundleContext)} method has been called
+ * @return true if the framework is stopping
+ */
+ public boolean isStopping() {
+ return stopping;
+ }
+
+ /**
+ * Returns the event publisher for this BaseAdaptor
+ * @return the event publisher for this BaseAdaptor
+ */
+ public EventPublisher getEventPublisher() {
+ return eventPublisher;
+ }
+
+ /**
+ * Returns the <code>HookRegistry</code> object for this adaptor.
+ * @return the <code>HookRegistry</code> object for this adaptor.
+ */
+ public HookRegistry getHookRegistry() {
+ return hookRegistry;
+ }
+
+ /**
+ * Returns the system bundle's context
+ * @return the system bundle's context
+ */
+ public BundleContext getContext() {
+ return context;
+ }
+
+ /**
+ * Returns the bundle with the specified identifier. This method
+ * does not invoke and bundle find hooks and therefore does not
+ * allow bundle find hooks to hide a bundle from the caller.
+ *
+ * @param id The identifier of the bundle to retrieve.
+ * @return A {@code Bundle} object or {@code null} if the identifier does
+ * not match any installed bundle.
+ */
+ public Bundle getBundle(long id) {
+ return eventPublisher.getBundle(id);
+ }
+
+ /**
+ * Creates a bundle file object for the given content and base data.
+ * This method must delegate to each configured bundle file factory
+ * {@link BundleFileFactoryHook#createBundleFile(Object, BaseData, boolean)} method until one
+ * factory returns a non-null value. If no bundle file factory returns a non-null value
+ * then the the default behavior will be performed.
+ * <p>
+ * If the specified content is <code>null</code> then the base content of the specified
+ * bundledata must be found before calling any bundle file factories.
+ * </p>
+ * <p>
+ * After the bundle file has been created each configured bundle file wrapper factory
+ * {@link BundleFileWrapperFactoryHook#wrapBundleFile(BundleFile, Object, BaseData, boolean)}
+ * method is called to wrap the bundle file.
+ * </p>
+ * @param content The object which contains the content of a bundle file. A value of
+ * <code>null</code> indicates that the storage must find the base content for the
+ * specified BaseData.
+ * @param data The BaseData associated with the content
+ * @return a BundleFile object.
+ * @throws IOException if an error occured while creating the BundleFile
+ */
+ public BundleFile createBundleFile(Object content, BaseData data) throws IOException {
+ return storage.createBundleFile(content, data);
+ }
+
+ /**
+ * Returns true if the persistent storage is read-only
+ * @return true if the persistent storage is read-only
+ */
+ public boolean isReadOnly() {
+ return storage.isReadOnly();
+ }
+
+ /*
+ * This is an experimental method to allow adaptors to replace the storage implementation by
+ * extending BaseAdaptor and overriding this method. This method is experimental.
+ * @return a base storage object.
+ */
+ protected BaseStorage getStorage() {
+ if (storage != null)
+ return storage;
+ // this bit of code assumes the registry is initialized with a BaseStorageHook
+ // we want to make sure we are using the same BaseStorage instance as the BaseStorageHook
+ StorageHook[] hooks = hookRegistry.getStorageHooks();
+ for (int i = 0; i < hooks.length && storage == null; i++)
+ if (hooks[i] instanceof BaseStorageHook)
+ storage = ((BaseStorageHook) hooks[i]).getStorage();
+ return storage;
+ }
+
+ /**
+ * @see FrameworkAdaptor#findEntries(List, String, String, int)
+ */
+ public Enumeration<URL> findEntries(List<BundleData> datas, String path, String filePattern, int options) {
+ List<BundleFile> bundleFiles = new ArrayList<BundleFile>(datas.size());
+ for (BundleData data : datas)
+ bundleFiles.add(((BaseData) data).getBundleFile());
+ // search all the bundle files
+ List<String> pathList = listEntryPaths(bundleFiles, path, filePattern, options);
+ // return null if no entries found
+ if (pathList.size() == 0)
+ return null;
+ // create an enumeration to enumerate the pathList
+ final String[] pathArray = pathList.toArray(new String[pathList.size()]);
+ final BundleData[] dataArray = datas.toArray(new BundleData[datas.size()]);
+ return new Enumeration<URL>() {
+ private int curPathIndex = 0;
+ private int curDataIndex = 0;
+ private URL nextElement = null;
+
+ public boolean hasMoreElements() {
+ if (nextElement != null)
+ return true;
+ getNextElement();
+ return nextElement != null;
+ }
+
+ public URL nextElement() {
+ if (!hasMoreElements())
+ throw new NoSuchElementException();
+ URL result = nextElement;
+ // force the next element search
+ getNextElement();
+ return result;
+ }
+
+ private void getNextElement() {
+ nextElement = null;
+ if (curPathIndex >= pathArray.length)
+ // reached the end of the pathArray; no more elements
+ return;
+ while (nextElement == null && curPathIndex < pathArray.length) {
+ String curPath = pathArray[curPathIndex];
+ // search the datas until we have searched them all
+ while (nextElement == null && curDataIndex < dataArray.length)
+ nextElement = dataArray[curDataIndex++].getEntry(curPath);
+ // we have searched all datas then advance to the next path
+ if (curDataIndex >= dataArray.length) {
+ curPathIndex++;
+ curDataIndex = 0;
+ }
+ }
+ }
+ };
+ }
+
+ /**
+ * Returns the names of resources available from a list of bundle files.
+ * No duplicate resource names are returned, each name is unique.
+ * @param bundleFiles the list of bundle files to search in
+ * @param path The path name in which to look.
+ * @param filePattern The file name pattern for selecting resource names in
+ * the specified path.
+ * @param options The options for listing resource names.
+ * @return a list of resource names. If no resources are found then
+ * the empty list is returned.
+ * @see BundleWiring#listResources(String, String, int)
+ */
+ public List<String> listEntryPaths(List<BundleFile> bundleFiles, String path, String filePattern, int options) {
+ // a list used to store the results of the search
+ List<String> pathList = new ArrayList<String>();
+ Filter patternFilter = null;
+ Hashtable<String, String> patternProps = null;
+ if (filePattern != null) {
+ // Optimization: If the file pattern does not include a wildcard or escape char then it must represent a single file.
+ // Avoid pattern matching and use BundleFile.getEntry() if recursion was not requested.
+ if ((options & BundleWiring.FINDENTRIES_RECURSE) == 0 && filePattern.indexOf('*') == -1 && filePattern.indexOf('\\') == -1) {
+ if (path.length() == 0)
+ path = filePattern;
+ else
+ path += path.charAt(path.length() - 1) == '/' ? filePattern : '/' + filePattern;
+ for (BundleFile bundleFile : bundleFiles) {
+ if (bundleFile.getEntry(path) != null && !pathList.contains(path))
+ pathList.add(path);
+ }
+ return pathList;
+ }
+ // For when the file pattern includes a wildcard.
+ try {
+ // create a file pattern filter with 'filename' as the key
+ patternFilter = FilterImpl.newInstance("(filename=" + sanitizeFilterInput(filePattern) + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ // create a single hashtable to be shared during the recursive search
+ patternProps = new Hashtable<String, String>(2);
+ } catch (InvalidSyntaxException e) {
+ // something unexpected happened; log error and return nothing
+ Bundle b = context == null ? null : context.getBundle();
+ eventPublisher.publishFrameworkEvent(FrameworkEvent.ERROR, b, e);
+ return pathList;
+ }
+ }
+ // find the entry paths for the datas
+ for (BundleFile bundleFile : bundleFiles) {
+ listEntryPaths(bundleFile, path, patternFilter, patternProps, options, pathList);
+ }
+ return pathList;
+ }
+
+ private String sanitizeFilterInput(String filePattern) throws InvalidSyntaxException {
+ StringBuffer buffer = null;
+ boolean foundEscape = false;
+ for (int i = 0; i < filePattern.length(); i++) {
+ char c = filePattern.charAt(i);
+ switch (c) {
+ case '\\' :
+ // we either used the escape found or found a new escape.
+ foundEscape = foundEscape ? false : true;
+ if (buffer != null)
+ buffer.append(c);
+ break;
+ case '(' :
+ case ')' :
+ if (!foundEscape) {
+ if (buffer == null) {
+ buffer = new StringBuffer(filePattern.length() + 16);
+ buffer.append(filePattern.substring(0, i));
+ }
+ // must escape with '\'
+ buffer.append('\\');
+ } else {
+ foundEscape = false; // used the escape found
+ }
+ if (buffer != null)
+ buffer.append(c);
+ break;
+ default :
+ // if we found an escape it has been used
+ foundEscape = false;
+ if (buffer != null)
+ buffer.append(c);
+ break;
+ }
+ }
+ if (foundEscape)
+ throw new InvalidSyntaxException("Trailing escape characters must be escaped.", filePattern); //$NON-NLS-1$
+ return buffer == null ? filePattern : buffer.toString();
+ }
+
+ private List<String> listEntryPaths(BundleFile bundleFile, String path, Filter patternFilter, Hashtable<String, String> patternProps, int options, List<String> pathList) {
+ if (pathList == null)
+ pathList = new ArrayList<String>();
+ Enumeration<String> entryPaths = bundleFile.getEntryPaths(path);
+ if (entryPaths == null)
+ return pathList;
+ while (entryPaths.hasMoreElements()) {
+ String entry = entryPaths.nextElement();
+ int lastSlash = entry.lastIndexOf('/');
+ if (patternProps != null) {
+ int secondToLastSlash = entry.lastIndexOf('/', lastSlash - 1);
+ int fileStart;
+ int fileEnd = entry.length();
+ if (lastSlash < 0)
+ fileStart = 0;
+ else if (lastSlash != entry.length() - 1)
+ fileStart = lastSlash + 1;
+ else {
+ fileEnd = lastSlash; // leave the lastSlash out
+ if (secondToLastSlash < 0)
+ fileStart = 0;
+ else
+ fileStart = secondToLastSlash + 1;
+ }
+ String fileName = entry.substring(fileStart, fileEnd);
+ // set the filename to the current entry
+ patternProps.put("filename", fileName); //$NON-NLS-1$
+ }
+ // prevent duplicates and match on the patternFilter
+ if (!pathList.contains(entry) && (patternFilter == null || patternFilter.matchCase(patternProps)))
+ pathList.add(entry);
+ // recurse only into entries that are directories
+ if (((options & BundleWiring.FINDENTRIES_RECURSE) != 0) && !entry.equals(path) && entry.length() > 0 && lastSlash == (entry.length() - 1))
+ listEntryPaths(bundleFile, entry, patternFilter, patternProps, options, pathList);
+ }
+ return pathList;
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/BaseData.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/BaseData.java
new file mode 100644
index 000000000..d7a910be6
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/BaseData.java
@@ -0,0 +1,530 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.baseadaptor;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.*;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile;
+import org.eclipse.osgi.baseadaptor.hooks.ClassLoadingHook;
+import org.eclipse.osgi.baseadaptor.hooks.StorageHook;
+import org.eclipse.osgi.baseadaptor.loader.BaseClassLoader;
+import org.eclipse.osgi.baseadaptor.loader.ClasspathManager;
+import org.eclipse.osgi.framework.adaptor.*;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.framework.internal.core.*;
+import org.eclipse.osgi.framework.internal.core.Constants;
+import org.eclipse.osgi.framework.internal.protocol.bundleentry.Handler;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+import org.eclipse.osgi.internal.baseadaptor.ArrayMap;
+import org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader;
+import org.eclipse.osgi.util.ManifestElement;
+import org.osgi.framework.*;
+
+/**
+ * The BundleData implementation used by the BaseAdaptor.
+ * @see BaseAdaptor
+ * @see BundleData
+ * @see StorageHook
+ * @see ClassLoadingHook
+ * @since 3.2
+ */
+public class BaseData implements BundleData {
+ private final static boolean COPY_NATIVES = Boolean.valueOf(FrameworkProperties.getProperty("osgi.classloader.copy.natives")).booleanValue(); //$NON-NLS-1$
+ private long id;
+ private BaseAdaptor adaptor;
+ private Bundle bundle;
+ private int startLevel = -1;
+ private int status = 0;
+ private StorageHook[] storageHooks;
+ private String location;
+ private long lastModified;
+ protected BundleFile bundleFile;
+ private ArrayMap<Object, BundleFile> bundleFiles;
+ private boolean dirty = false;
+ protected Dictionary<String, String> manifest;
+ // This field is only used by PDE source lookup, and is set by a hook (bug 126517). It serves no purpose at runtime.
+ protected String fileName;
+ // This is only used to keep track of when the same native library is loaded more than once
+ protected Collection<String> loadedNativeCode;
+
+ ///////////////////// Begin values from Manifest /////////////////////
+ private String symbolicName;
+ private Version version;
+ private String activator;
+ private String classpath;
+ private String executionEnvironment;
+ private String dynamicImports;
+ private int type;
+
+ ///////////////////// End values from Manifest /////////////////////
+
+ /**
+ * Constructs a new BaseData with the specified id for the specified adaptor
+ * @param id the id of the BaseData
+ * @param adaptor the adaptor of the BaseData
+ */
+ public BaseData(long id, BaseAdaptor adaptor) {
+ this.id = id;
+ this.adaptor = adaptor;
+ }
+
+ /**
+ * This method calls all the configured class loading hooks {@link ClassLoadingHook#createClassLoader(ClassLoader, ClassLoaderDelegate, BundleProtectionDomain, BaseData, String[])}
+ * methods until on returns a non-null value. If none of the class loading hooks returns a non-null value
+ * then the default classloader implementation is used.
+ * @see BundleData#createClassLoader(ClassLoaderDelegate, BundleProtectionDomain, String[])
+ */
+ public BundleClassLoader createClassLoader(ClassLoaderDelegate delegate, BundleProtectionDomain domain, String[] bundleclasspath) {
+ ClassLoadingHook[] hooks = adaptor.getHookRegistry().getClassLoadingHooks();
+ ClassLoader parent = adaptor.getBundleClassLoaderParent();
+ BaseClassLoader cl = null;
+ for (int i = 0; i < hooks.length && cl == null; i++)
+ cl = hooks[i].createClassLoader(parent, delegate, domain, this, bundleclasspath);
+ if (cl == null)
+ cl = new DefaultClassLoader(parent, delegate, domain, this, bundleclasspath);
+ return cl;
+ }
+
+ public final URL getEntry(final String path) {
+ if (System.getSecurityManager() == null)
+ return getEntry0(path);
+ return AccessController.doPrivileged(new PrivilegedAction<URL>() {
+ public URL run() {
+ return getEntry0(path);
+ }
+ });
+ }
+
+ final URL getEntry0(String path) {
+ BundleEntry entry = getBundleFile().getEntry(path);
+ if (entry == null)
+ return null;
+ path = BundleFile.fixTrailingSlash(path, entry);
+ try {
+ //use the constant string for the protocol to prevent duplication
+ return new URL(Constants.OSGI_ENTRY_URL_PROTOCOL, Long.toString(id) + BundleResourceHandler.BID_FWKID_SEPARATOR + Integer.toString(adaptor.hashCode()), 0, path, new Handler(entry, adaptor));
+ } catch (MalformedURLException e) {
+ return null;
+ }
+ }
+
+ public final Enumeration<String> getEntryPaths(String path) {
+ return getBundleFile().getEntryPaths(path);
+ }
+
+ /**
+ * This method calls each configured classloading hook {@link ClassLoadingHook#findLibrary(BaseData, String)} method
+ * until the first one returns a non-null value.
+ * @see BundleData#findLibrary(String)
+ */
+ public String findLibrary(String libname) {
+ ClassLoadingHook[] hooks = adaptor.getHookRegistry().getClassLoadingHooks();
+ String result = null;
+ for (int i = 0; i < hooks.length; i++) {
+ result = hooks[i].findLibrary(this, libname);
+ if (result != null)
+ break;
+ }
+ // check to see if this library has been loaded by another class loader
+ if (result != null)
+ synchronized (this) {
+ if (loadedNativeCode == null)
+ loadedNativeCode = new ArrayList<String>(1);
+ if (loadedNativeCode.contains(result) || COPY_NATIVES) {
+ // we must copy the library to a temp space to allow another class loader to load the library
+ String temp = copyToTempLibrary(result);
+ if (temp != null)
+ result = temp;
+ } else {
+ loadedNativeCode.add(result);
+ }
+ }
+ return result;
+ }
+
+ private String copyToTempLibrary(String result) {
+ try {
+ return adaptor.getStorage().copyToTempLibrary(this, result);
+ } catch (IOException e) {
+ adaptor.getFrameworkLog().log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, e.getMessage(), 0, e, null));
+ }
+ return null;
+ }
+
+ public void installNativeCode(String[] nativepaths) throws BundleException {
+ adaptor.getStorage().installNativeCode(this, nativepaths);
+ }
+
+ public File getDataFile(String path) {
+ return adaptor.getStorage().getDataFile(this, path);
+ }
+
+ public Dictionary<String, String> getManifest() throws BundleException {
+ if (manifest == null)
+ manifest = adaptor.getStorage().loadManifest(this);
+ return manifest;
+ }
+
+ public long getBundleID() {
+ return id;
+ }
+
+ public final String getLocation() {
+ return location;
+ }
+
+ /**
+ * Sets the location of this bundledata
+ * @param location the location of this bundledata
+ */
+ public final void setLocation(String location) {
+ this.location = location;
+ }
+
+ public final long getLastModified() {
+ return lastModified;
+ }
+
+ /**
+ * Sets the last modified time stamp of this bundledata
+ * @param lastModified the last modified time stamp of this bundledata
+ */
+ public final void setLastModified(long lastModified) {
+ this.lastModified = lastModified;
+ }
+
+ public synchronized void close() throws IOException {
+ if (bundleFile != null)
+ getBundleFile().close(); // only close the bundleFile if it already exists.
+ if (bundleFiles != null) {
+ for (BundleFile bundlefile : bundleFiles.getValues())
+ bundlefile.close();
+ bundleFiles.clear();
+ }
+ }
+
+ public void open() throws IOException {
+ getBundleFile().open();
+ }
+
+ public final void setBundle(Bundle bundle) {
+ this.bundle = bundle;
+ }
+
+ /**
+ * Returns the bundle object of this BaseData
+ * @return the bundle object of this BaseData
+ */
+ public final Bundle getBundle() {
+ return bundle;
+ }
+
+ public int getStartLevel() {
+ return startLevel;
+ }
+
+ public int getStatus() {
+ return status;
+ }
+
+ /**
+ * This method calls each configured storage hook {@link StorageHook#forgetStartLevelChange(int)} method.
+ * If one returns true then this bundledata is not marked dirty.
+ * @see BundleData#setStartLevel(int)
+ */
+ public void setStartLevel(int value) {
+ startLevel = setPersistentData(value, true, startLevel);
+ }
+
+ /**
+ * This method calls each configured storage hook {@link StorageHook#forgetStatusChange(int)} method.
+ * If one returns true then this bundledata is not marked dirty.
+ * @see BundleData#setStatus(int)
+ */
+ public void setStatus(int value) {
+ status = setPersistentData(value, false, status);
+ }
+
+ private int setPersistentData(int value, boolean isStartLevel, int orig) {
+ StorageHook[] hooks = getStorageHooks();
+ for (int i = 0; i < hooks.length; i++)
+ if (isStartLevel) {
+ if (hooks[i].forgetStartLevelChange(value))
+ return value;
+ } else {
+ if (hooks[i].forgetStatusChange(value))
+ return value;
+ }
+ if (value != orig)
+ dirty = true;
+ return value;
+ }
+
+ /**
+ * @throws IOException
+ */
+ public void save() throws IOException {
+ adaptor.getStorage().save(this);
+ }
+
+ /**
+ * Returns true if this bundledata is dirty
+ * @return true if this bundledata is dirty
+ */
+ public boolean isDirty() {
+ return dirty;
+ }
+
+ /**
+ * Sets the dirty flag for this BaseData
+ * @param dirty the dirty flag
+ */
+ public void setDirty(boolean dirty) {
+ this.dirty = dirty;
+ }
+
+ public final String getSymbolicName() {
+ return symbolicName;
+ }
+
+ /**
+ * Sets the symbolic name of this BaseData
+ * @param symbolicName the symbolic name
+ */
+ public final void setSymbolicName(String symbolicName) {
+ this.symbolicName = symbolicName;
+ }
+
+ public final Version getVersion() {
+ return version;
+ }
+
+ /**
+ * Sets the version of this BaseData
+ * @param version the version
+ */
+ public final void setVersion(Version version) {
+ this.version = version;
+ }
+
+ public final int getType() {
+ return type;
+ }
+
+ /**
+ * Sets the type of this BaseData
+ * @param type the type
+ */
+ public final void setType(int type) {
+ this.type = type;
+ }
+
+ public final String[] getClassPath() throws BundleException {
+ ManifestElement[] classpathElements = ManifestElement.parseHeader(Constants.BUNDLE_CLASSPATH, classpath);
+ return getClassPath(classpathElements);
+ }
+
+ // TODO make classpath a String[] instead of saving a comma separated string.
+ public String getClassPathString() {
+ return classpath;
+ }
+
+ //TODO make classpath a String[] instead of saving a comma separated string.
+ public void setClassPathString(String classpath) {
+ this.classpath = classpath;
+ }
+
+ public final String getActivator() {
+ return activator;
+ }
+
+ /**
+ * Sets the activator of this BaseData
+ * @param activator the activator
+ */
+ public final void setActivator(String activator) {
+ this.activator = activator;
+ }
+
+ public final String getExecutionEnvironment() {
+ return executionEnvironment;
+ }
+
+ /**
+ * Sets the execution environment of this BaseData
+ * @param executionEnvironment the execution environment
+ */
+ public void setExecutionEnvironment(String executionEnvironment) {
+ this.executionEnvironment = executionEnvironment;
+ }
+
+ public final String getDynamicImports() {
+ return dynamicImports;
+ }
+
+ /**
+ * Sets the dynamic imports of this BaseData
+ * @param dynamicImports the dynamic imports
+ */
+ public void setDynamicImports(String dynamicImports) {
+ this.dynamicImports = dynamicImports;
+ }
+
+ /**
+ * Returns the adaptor for this BaseData
+ * @return the adaptor
+ */
+ public final BaseAdaptor getAdaptor() {
+ return adaptor;
+ }
+
+ /**
+ * Returns the BundleFile for this BaseData. The first time this method is called the
+ * configured storage {@link BaseAdaptor#createBundleFile(Object, BaseData)} method is called.
+ * @return the BundleFile
+ * @throws IllegalArgumentException
+ */
+ public synchronized BundleFile getBundleFile() throws IllegalArgumentException {
+ if (bundleFile == null)
+ try {
+ bundleFile = adaptor.createBundleFile(null, this);
+ } catch (IOException e) {
+ throw (IllegalArgumentException) new IllegalArgumentException(e.getMessage()).initCause(e);
+ }
+ return bundleFile;
+ }
+
+ public synchronized BundleFile getBundleFile(Object content, boolean base) {
+ return base ? bundleFile : (bundleFiles == null) ? null : bundleFiles.get(content);
+ }
+
+ public synchronized void setBundleFile(Object content, BundleFile bundleFile) {
+ if (bundleFiles == null)
+ bundleFiles = new ArrayMap<Object, BundleFile>(1);
+ bundleFiles.put(content, bundleFile);
+ }
+
+ private static String[] getClassPath(ManifestElement[] classpath) {
+ if (classpath == null) {
+ if (Debug.DEBUG_LOADER)
+ Debug.println(" no classpath"); //$NON-NLS-1$
+ /* create default BundleClassPath */
+ return new String[] {"."}; //$NON-NLS-1$
+ }
+
+ List<String> result = new ArrayList<String>(classpath.length);
+ for (int i = 0; i < classpath.length; i++) {
+ if (Debug.DEBUG_LOADER)
+ Debug.println(" found classpath entry " + classpath[i].getValueComponents()); //$NON-NLS-1$
+ String[] paths = classpath[i].getValueComponents();
+ for (int j = 0; j < paths.length; j++) {
+ result.add(paths[j]);
+ }
+ }
+
+ return result.toArray(new String[result.size()]);
+ }
+
+ /**
+ * Returns the storage hook which is keyed by the specified key
+ * @param key the key of the storage hook to get
+ * @return the storage hook which is keyed by the specified key
+ */
+ public StorageHook getStorageHook(String key) {
+ if (storageHooks == null)
+ return null;
+ for (int i = 0; i < storageHooks.length; i++)
+ if (storageHooks[i].getKey().equals(key))
+ return storageHooks[i];
+ return null;
+ }
+
+ /**
+ * Sets the instance storage hooks for this base data. This is method
+ * may only be called once for the lifetime of the base data. Once set,
+ * the list of storage hooks remains constant.
+ * @param storageHooks the storage hook to add
+ */
+ public void setStorageHooks(StorageHook[] storageHooks) {
+ if (this.storageHooks != null)
+ return; // only allow this to be set once.
+ this.storageHooks = storageHooks;
+ }
+
+ /**
+ * Returns all the storage hooks associated with this BaseData
+ * @return all the storage hooks associated with this BaseData
+ */
+ public StorageHook[] getStorageHooks() {
+ return storageHooks == null ? new StorageHook[0] : storageHooks;
+ }
+
+ /**
+ * Gets called by BundleFile during {@link BundleFile#getFile(String, boolean)}. This method
+ * will allocate a File object where content of the specified path may be
+ * stored for the current generation of the base data. The returned File object may
+ * not exist if the content has not previously be stored.
+ * @param path the path to the content to extract from the base data
+ * @return a file object where content of the specified path may be stored.
+ */
+ public File getExtractFile(String path) {
+ return adaptor.getStorage().getExtractFile(this, path);
+ }
+
+ /**
+ * This is only used to support PDE source lookup. The field named &quot;fileName&quot;
+ * must be set for PDE to access the absolute path string.
+ * @param fileName an absolute path string to the base bundle file.
+ */
+ // This is only done for PDE source lookup (bug 126517)
+ public void setFileName(String fileName) {
+ this.fileName = fileName;
+ }
+
+ /**
+ * Return a string representation of the bundle that can be used in debug messages.
+ *
+ * @return String representation of the bundle
+ */
+ public String toString() {
+ String name = getSymbolicName();
+ if (name == null)
+ return getLocation();
+ Version ver = getVersion();
+ if (ver == null)
+ return name;
+ return name + "_" + ver; //$NON-NLS-1$
+ }
+
+ public Enumeration<URL> findLocalResources(String path) {
+ String[] cp;
+ try {
+ cp = getClassPath();
+ } catch (BundleException e) {
+ cp = new String[0];
+ }
+ if (cp == null)
+ cp = new String[0];
+ // this is not optimized; degenerate case of someone calling getResource on an unresolved bundle!
+ ClasspathManager cm = new ClasspathManager(this, cp, null);
+ cm.initialize();
+ Enumeration<URL> result = cm.findLocalResources(path);
+ // no need to close ClassPathManager because the BundleFile objects are stored in the BaseData and closed on shutdown or uninstall
+ return result;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/HookConfigurator.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/HookConfigurator.java
new file mode 100644
index 000000000..e203a1104
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/HookConfigurator.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.baseadaptor;
+
+/**
+ * A hook configurator is used to add hooks to the hook registry.
+ * @see HookRegistry
+ * @since 3.2
+ */
+public interface HookConfigurator {
+ /**
+ * Adds hooks to the specified hook registry.
+ * @param hookRegistry the hook registry used to add hooks
+ */
+ public void addHooks(HookRegistry hookRegistry);
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/HookRegistry.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/HookRegistry.java
new file mode 100644
index 000000000..e0c91e0c8
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/HookRegistry.java
@@ -0,0 +1,334 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.baseadaptor;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.*;
+import org.eclipse.osgi.baseadaptor.hooks.*;
+import org.eclipse.osgi.framework.adaptor.*;
+import org.eclipse.osgi.framework.internal.core.FrameworkProperties;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+import org.eclipse.osgi.util.ManifestElement;
+
+/**
+ * The hook registry is used to store all the hooks which are
+ * configured by the hook configurators.
+ * @see HookConfigurator
+ * @since 3.2
+ */
+public final class HookRegistry {
+ /**
+ * The hook configurators properties file (&quot;hookconfigurators.properties&quot;) <p>
+ * A framework extension may supply a hook configurators properties file to specify a
+ * list of hook configurators.
+ * @see #HOOK_CONFIGURATORS
+ */
+ public static final String HOOK_CONFIGURATORS_FILE = "hookconfigurators.properties"; //$NON-NLS-1$
+
+ /**
+ * The hook configurators property key (&quot;hookconfigurators.properties&quot;) used in
+ * a hook configurators properties file to specify a comma separated list of fully
+ * qualified hook configurator classes.
+ */
+ public static final String HOOK_CONFIGURATORS = "hook.configurators"; //$NON-NLS-1$
+
+ /**
+ * A system property (&quot;osgi.hook.configurators.include&quot;) used to add additional
+ * hook configurators. This is helpful for configuring optional hook configurators.
+ */
+ public static final String PROP_HOOK_CONFIGURATORS_INCLUDE = "osgi.hook.configurators.include"; //$NON-NLS-1$
+
+ /**
+ * A system property (&quot;osgi.hook.configurators.exclude&quot;) used to exclude
+ * any hook configurators. This is helpful for disabling hook
+ * configurators that is specified in hook configurator properties files.
+ */
+ public static final String PROP_HOOK_CONFIGURATORS_EXCLUDE = "osgi.hook.configurators.exclude"; //$NON-NLS-1$
+
+ /**
+ * A system property (&quot;osgi.hook.configurators&quot;) used to specify the list
+ * of hook configurators. If this property is set then the list of configurators
+ * specified will be the only configurators used.
+ */
+ public static final String PROP_HOOK_CONFIGURATORS = "osgi.hook.configurators"; //$NON-NLS-1$
+
+ private static final String BUILTIN_HOOKS = "builtin.hooks"; //$NON-NLS-1$
+
+ private BaseAdaptor adaptor;
+ private boolean readonly = false;
+ private AdaptorHook[] adaptorHooks = new AdaptorHook[0];
+ private BundleWatcher[] watchers = new BundleWatcher[0];
+ private ClassLoadingHook[] classLoadingHooks = new ClassLoadingHook[0];
+ private ClassLoadingStatsHook[] classLoadingStatsHooks = new ClassLoadingStatsHook[0];
+ private ClassLoaderDelegateHook[] classLoaderDelegateHooks = new ClassLoaderDelegateHook[0];
+ private StorageHook[] storageHooks = new StorageHook[0];
+ private BundleFileFactoryHook[] bundleFileFactoryHooks = new BundleFileFactoryHook[0];
+ private BundleFileWrapperFactoryHook[] bundleFileWrapperFactoryHooks = new BundleFileWrapperFactoryHook[0];
+
+ public HookRegistry(BaseAdaptor adaptor) {
+ this.adaptor = adaptor;
+ }
+
+ /**
+ * Initializes the hook configurators. The following steps are used to initialize the hook configurators. <p>
+ * 1. Get a list of hook configurators from all hook configurators properties files on the classpath,
+ * add this list to the overall list of hook configurators, remove duplicates. <p>
+ * 2. Get a list of hook configurators from the (&quot;osgi.hook.configurators.include&quot;) system property
+ * and add this list to the overall list of hook configurators, remove duplicates. <p>
+ * 3. Get a list of hook configurators from the (&quot;osgi.hook.configurators.exclude&quot;) system property
+ * and remove this list from the overall list of hook configurators. <p>
+ * 4. Load each hook configurator class, create a new instance, then call the {@link HookConfigurator#addHooks(HookRegistry)} method <p>
+ * 5. Set this HookRegistry object to read only to prevent any other hooks from being added. <p>
+ * @return an array of error log entries that occurred while initializing the hooks
+ */
+ public FrameworkLogEntry[] initialize() {
+ List<String> configurators = new ArrayList<String>(5);
+ List<FrameworkLogEntry> errors = new ArrayList<FrameworkLogEntry>(0); // optimistic that no errors will occur
+ mergeFileHookConfigurators(configurators, errors);
+ mergePropertyHookConfigurators(configurators);
+ loadConfigurators(configurators, errors);
+ // set to read-only
+ readonly = true;
+ return errors.toArray(new FrameworkLogEntry[errors.size()]);
+ }
+
+ private void mergeFileHookConfigurators(List<String> configuratorList, List<FrameworkLogEntry> errors) {
+ ClassLoader cl = getClass().getClassLoader();
+ // get all hook configurators files in your classloader delegation
+ Enumeration<URL> hookConfigurators;
+ try {
+ hookConfigurators = cl != null ? cl.getResources(HookRegistry.HOOK_CONFIGURATORS_FILE) : ClassLoader.getSystemResources(HookRegistry.HOOK_CONFIGURATORS_FILE);
+ } catch (IOException e) {
+ errors.add(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, "getResources error on " + HookRegistry.HOOK_CONFIGURATORS_FILE, 0, e, null)); //$NON-NLS-1$
+ return;
+ }
+ int curBuiltin = 0;
+ while (hookConfigurators.hasMoreElements()) {
+ URL url = hookConfigurators.nextElement();
+ InputStream input = null;
+ try {
+ // check each file for a hook.configurators property
+ Properties configuratorProps = new Properties();
+ input = url.openStream();
+ configuratorProps.load(input);
+ String hooksValue = configuratorProps.getProperty(HOOK_CONFIGURATORS);
+ if (hooksValue == null)
+ continue;
+ boolean builtin = Boolean.valueOf(configuratorProps.getProperty(BUILTIN_HOOKS)).booleanValue();
+ String[] configurators = ManifestElement.getArrayFromList(hooksValue, ","); //$NON-NLS-1$
+ for (int i = 0; i < configurators.length; i++)
+ if (!configuratorList.contains(configurators[i])) {
+ if (builtin) // make sure the built-in configurators are listed first (bug 170881)
+ configuratorList.add(curBuiltin++, configurators[i]);
+ else
+ configuratorList.add(configurators[i]);
+ }
+ } catch (IOException e) {
+ errors.add(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, "error loading: " + url.toExternalForm(), 0, e, null)); //$NON-NLS-1$
+ // ignore and continue to next URL
+ } finally {
+ if (input != null)
+ try {
+ input.close();
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+ }
+ }
+
+ private void mergePropertyHookConfigurators(List<String> configuratorList) {
+ // see if there is a configurators list
+ String[] configurators = ManifestElement.getArrayFromList(FrameworkProperties.getProperty(HookRegistry.PROP_HOOK_CONFIGURATORS), ","); //$NON-NLS-1$
+ if (configurators.length > 0) {
+ configuratorList.clear(); // clear the list, we are only going to use the configurators from the list
+ for (int i = 0; i < configurators.length; i++)
+ if (!configuratorList.contains(configurators[i]))
+ configuratorList.add(configurators[i]);
+ return; // don't do anything else
+ }
+ // Make sure the configurators from the include property are in the list
+ String[] includeConfigurators = ManifestElement.getArrayFromList(FrameworkProperties.getProperty(HookRegistry.PROP_HOOK_CONFIGURATORS_INCLUDE), ","); //$NON-NLS-1$
+ for (int i = 0; i < includeConfigurators.length; i++)
+ if (!configuratorList.contains(includeConfigurators[i]))
+ configuratorList.add(includeConfigurators[i]);
+ // Make sure the configurators from the exclude property are no in the list
+ String[] excludeHooks = ManifestElement.getArrayFromList(FrameworkProperties.getProperty(HookRegistry.PROP_HOOK_CONFIGURATORS_EXCLUDE), ","); //$NON-NLS-1$
+ for (int i = 0; i < excludeHooks.length; i++)
+ configuratorList.remove(excludeHooks[i]);
+ }
+
+ private void loadConfigurators(List<String> configurators, List<FrameworkLogEntry> errors) {
+ for (Iterator<String> iHooks = configurators.iterator(); iHooks.hasNext();) {
+ String hookName = iHooks.next();
+ try {
+ Class<?> clazz = Class.forName(hookName);
+ HookConfigurator configurator = (HookConfigurator) clazz.newInstance();
+ configurator.addHooks(this);
+ } catch (Throwable t) {
+ // We expect the follow exeptions may happen; but we need to catch all here
+ // ClassNotFoundException
+ // IllegalAccessException
+ // InstantiationException
+ // ClassCastException
+ errors.add(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, "error loading hook: " + hookName, 0, t, null)); //$NON-NLS-1$
+ }
+ }
+ }
+
+ /**
+ * Returns the list of configured adaptor hooks.
+ * @return the list of configured adaptor hooks.
+ */
+ public AdaptorHook[] getAdaptorHooks() {
+ return adaptorHooks;
+ }
+
+ /**
+ * Returns the list of configured bundle watchers.
+ * @return the list of configured bundle watchers.
+ */
+ public BundleWatcher[] getWatchers() {
+ return watchers;
+ }
+
+ /**
+ * Returns the list of configured class loading hooks.
+ * @return the list of configured class loading hooks.
+ */
+ public ClassLoadingHook[] getClassLoadingHooks() {
+ return classLoadingHooks;
+ }
+
+ /**
+ * Returns the list of configured class loading stats hooks.
+ * @return the list of configured class loading stats hooks.
+ */
+ public ClassLoadingStatsHook[] getClassLoadingStatsHooks() {
+ return classLoadingStatsHooks;
+ }
+
+ /**
+ * Returns the list of configured class loader delegate hooks.
+ * @return the list of configured class loader delegate hooks.
+ */
+ public ClassLoaderDelegateHook[] getClassLoaderDelegateHooks() {
+ return classLoaderDelegateHooks;
+ }
+
+ /**
+ * Returns the list of configured storage hooks.
+ * @return the list of configured storage hooks.
+ */
+ public StorageHook[] getStorageHooks() {
+ return storageHooks;
+ }
+
+ /**
+ * Returns the list of configured bundle file factories.
+ * @return the list of configured bundle file factories.
+ */
+ public BundleFileFactoryHook[] getBundleFileFactoryHooks() {
+ return bundleFileFactoryHooks;
+ }
+
+ /**
+ * Returns the configured bundle file wrapper factories
+ * @return the configured bundle file wrapper factories
+ */
+ public BundleFileWrapperFactoryHook[] getBundleFileWrapperFactoryHooks() {
+ return bundleFileWrapperFactoryHooks;
+ }
+
+ /**
+ * Adds a adaptor hook to this hook registry.
+ * @param adaptorHook an adaptor hook object.
+ */
+ public void addAdaptorHook(AdaptorHook adaptorHook) {
+ adaptorHooks = (AdaptorHook[]) add(adaptorHook, adaptorHooks, new AdaptorHook[adaptorHooks.length + 1]);
+ }
+
+ /**
+ * Adds a bundle watcher to this hook registry.
+ * @param watcher a bundle watcher object.
+ */
+ public void addWatcher(BundleWatcher watcher) {
+ watchers = (BundleWatcher[]) add(watcher, watchers, new BundleWatcher[watchers.length + 1]);
+ }
+
+ /**
+ * Adds a class loading hook to this hook registry.
+ * @param classLoadingHook a class loading hook object.
+ */
+ public void addClassLoadingHook(ClassLoadingHook classLoadingHook) {
+ classLoadingHooks = (ClassLoadingHook[]) add(classLoadingHook, classLoadingHooks, new ClassLoadingHook[classLoadingHooks.length + 1]);
+ }
+
+ /**
+ * Adds a class loading stats hook to this hook registry.
+ * @param classLoadingStatsHook a class loading hook object.
+ */
+ public void addClassLoadingStatsHook(ClassLoadingStatsHook classLoadingStatsHook) {
+ classLoadingStatsHooks = (ClassLoadingStatsHook[]) add(classLoadingStatsHook, classLoadingStatsHooks, new ClassLoadingStatsHook[classLoadingStatsHooks.length + 1]);
+ }
+
+ /**
+ * Adds a class loader delegate hook to this hook registry.
+ * @param classLoaderDelegateHook a class loader delegate hook.
+ */
+ public void addClassLoaderDelegateHook(ClassLoaderDelegateHook classLoaderDelegateHook) {
+ classLoaderDelegateHooks = (ClassLoaderDelegateHook[]) add(classLoaderDelegateHook, classLoaderDelegateHooks, new ClassLoaderDelegateHook[classLoaderDelegateHooks.length + 1]);
+ }
+
+ /**
+ * Adds a storage hook to this hook registry.
+ * @param storageHook a storage hook object.
+ */
+ public void addStorageHook(StorageHook storageHook) {
+ storageHooks = (StorageHook[]) add(storageHook, storageHooks, new StorageHook[storageHooks.length + 1]);
+ }
+
+ /**
+ * Adds a bundle file factory to this hook registry.
+ * @param factory a bundle file factory object.
+ */
+ public void addBundleFileFactoryHook(BundleFileFactoryHook factory) {
+ bundleFileFactoryHooks = (BundleFileFactoryHook[]) add(factory, bundleFileFactoryHooks, new BundleFileFactoryHook[bundleFileFactoryHooks.length + 1]);
+ }
+
+ /**
+ * Adds a bundle file wrapper factory for this hook registry
+ * @param factory a bundle file wrapper factory object.
+ */
+ public void addBundleFileWrapperFactoryHook(BundleFileWrapperFactoryHook factory) {
+ bundleFileWrapperFactoryHooks = (BundleFileWrapperFactoryHook[]) add(factory, bundleFileWrapperFactoryHooks, new BundleFileWrapperFactoryHook[bundleFileWrapperFactoryHooks.length + 1]);
+ }
+
+ private Object[] add(Object newValue, Object[] oldValues, Object[] newValues) {
+ if (readonly)
+ throw new IllegalStateException("Cannot add hooks dynamically."); //$NON-NLS-1$
+ if (oldValues.length > 0)
+ System.arraycopy(oldValues, 0, newValues, 0, oldValues.length);
+ newValues[oldValues.length] = newValue;
+ return newValues;
+ }
+
+ /**
+ * Returns the base adaptor associated with this hook registry.
+ * @return the base adaptor associated with this hook registry.
+ */
+ public BaseAdaptor getAdaptor() {
+ return adaptor;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/BundleEntry.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/BundleEntry.java
new file mode 100644
index 000000000..b9d48f3ae
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/BundleEntry.java
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.baseadaptor.bundlefile;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.internal.baseadaptor.AdaptorUtil;
+
+/**
+ * A BundleEntry represents one entry of a BundleFile.
+ * <p>
+ * Clients may extend this class.
+ * </p>
+ * @since 3.2
+ */
+public abstract class BundleEntry {
+ protected static final int BUF_SIZE = 8 * 1024;
+
+ /**
+ * Return an InputStream for the entry.
+ *
+ * @return InputStream for the entry.
+ * @throws java.io.IOException If an error occurs reading the bundle.
+ */
+ public abstract InputStream getInputStream() throws IOException;
+
+ /**
+ * Return the size of the entry (uncompressed).
+ *
+ * @return size of entry.
+ */
+ public abstract long getSize();
+
+ /**
+ * Return the name of the entry.
+ *
+ * @return name of entry.
+ */
+ public abstract String getName();
+
+ /**
+ * Get the modification time for this BundleEntry.
+ * <p>If the modification time has not been set,
+ * this method will return <tt>-1</tt>.
+ *
+ * @return last modification time.
+ */
+ public abstract long getTime();
+
+ /**
+ * Get a URL to the bundle entry that uses a common protocol (i.e. file:
+ * jar: or http: etc.).
+ * @return a URL to the bundle entry that uses a common protocol
+ */
+ public abstract URL getLocalURL();
+
+ /**
+ * Get a URL to the content of the bundle entry that uses the file: protocol.
+ * The content of the bundle entry may be downloaded or extracted to the local
+ * file system in order to create a file: URL.
+ * @return a URL to the content of the bundle entry that uses the file: protocol
+ */
+ public abstract URL getFileURL();
+
+ /**
+ * Return the name of this BundleEntry by calling getName().
+ *
+ * @return String representation of this BundleEntry.
+ */
+ public String toString() {
+ return (getName());
+ }
+
+ /**
+ * Used for class loading. This default implementation gets the input stream from this entry
+ * and copies the content into a byte array.
+ * @return a byte array containing the content of this entry
+ * @throws IOException
+ */
+ public byte[] getBytes() throws IOException {
+ InputStream in = getInputStream();
+ int length = (int) getSize();
+ if (Debug.DEBUG_LOADER)
+ Debug.println(" about to read " + length + " bytes from " + getName()); //$NON-NLS-1$ //$NON-NLS-2$
+ return AdaptorUtil.getBytes(in, length, BUF_SIZE);
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/BundleFile.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/BundleFile.java
new file mode 100644
index 000000000..011359dab
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/BundleFile.java
@@ -0,0 +1,232 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.baseadaptor.bundlefile;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.AccessController;
+import java.util.*;
+import org.eclipse.osgi.baseadaptor.BaseData;
+import org.eclipse.osgi.framework.internal.core.*;
+import org.eclipse.osgi.framework.internal.protocol.bundleresource.Handler;
+import org.eclipse.osgi.framework.util.SecureAction;
+import org.eclipse.osgi.util.ManifestElement;
+
+/**
+ * The BundleFile API is used by Adaptors to read resources out of an
+ * installed Bundle in the Framework.
+ * <p>
+ * Clients may extend this class.
+ * </p>
+ * @since 3.2
+ */
+abstract public class BundleFile {
+ protected static final String PROP_SETPERMS_CMD = "osgi.filepermissions.command"; //$NON-NLS-1$
+ static final SecureAction secureAction = AccessController.doPrivileged(SecureAction.createSecureAction());
+ /**
+ * The File object for this BundleFile.
+ */
+ protected File basefile;
+ private int mruIndex = -1;
+
+ /**
+ * Default constructor
+ *
+ */
+ public BundleFile() {
+ // do nothing
+ }
+
+ /**
+ * BundleFile constructor
+ * @param basefile The File object where this BundleFile is
+ * persistently stored.
+ */
+ public BundleFile(File basefile) {
+ this.basefile = basefile;
+ }
+
+ /**
+ * Returns a File for the bundle entry specified by the path.
+ * If required the content of the bundle entry is extracted into a file
+ * on the file system.
+ * @param path The path to the entry to locate a File for.
+ * @param nativeCode true if the path is native code.
+ * @return A File object to access the contents of the bundle entry.
+ */
+ abstract public File getFile(String path, boolean nativeCode);
+
+ /**
+ * Locates a file name in this bundle and returns a BundleEntry object
+ *
+ * @param path path of the entry to locate in the bundle
+ * @return BundleEntry object or null if the file name
+ * does not exist in the bundle
+ */
+ abstract public BundleEntry getEntry(String path);
+
+ /**
+ * Allows to access the entries of the bundle.
+ * Since the bundle content is usually a jar, this
+ * allows to access the jar contents.
+ *
+ * GetEntryPaths allows to enumerate the content of "path".
+ * If path is a directory, it is equivalent to listing the directory
+ * contents. The returned names are either files or directories
+ * themselves. If a returned name is a directory, it finishes with a
+ * slash. If a returned name is a file, it does not finish with a slash.
+ * @param path path of the entry to locate in the bundle
+ * @return an Enumeration of Strings that indicate the paths found or
+ * null if the path does not exist.
+ */
+ abstract public Enumeration<String> getEntryPaths(String path);
+
+ /**
+ * Closes the BundleFile.
+ * @throws IOException if any error occurs.
+ */
+ abstract public void close() throws IOException;
+
+ /**
+ * Opens the BundleFiles.
+ * @throws IOException if any error occurs.
+ */
+ abstract public void open() throws IOException;
+
+ /**
+ * Determines if any BundleEntries exist in the given directory path.
+ * @param dir The directory path to check existence of.
+ * @return true if the BundleFile contains entries under the given directory path;
+ * false otherwise.
+ */
+ abstract public boolean containsDir(String dir);
+
+ /**
+ * Returns a URL to access the contents of the entry specified by the path
+ * @param path the path to the resource
+ * @param hostBundleID the host bundle ID
+ * @return a URL to access the contents of the entry specified by the path
+ * @deprecated use {@link #getResourceURL(String, BaseData, int)}
+ */
+ public URL getResourceURL(String path, long hostBundleID) {
+ return getResourceURL(path, hostBundleID, 0);
+ }
+
+ /**
+ * Returns a URL to access the contents of the entry specified by the path
+ * @param path the path to the resource
+ * @param hostBundleID the host bundle ID
+ * @param index the resource index
+ * @return a URL to access the contents of the entry specified by the path
+ * @deprecated use {@link #getResourceURL(String, BaseData, int)}
+ */
+ public URL getResourceURL(String path, long hostBundleID, int index) {
+ return internalGetResourceURL(path, null, hostBundleID, index);
+ }
+
+ /**
+ * Returns a URL to access the contents of the entry specified by the path
+ * @param path the path to the resource
+ * @param hostData the host BaseData
+ * @param index the resource index
+ * @return a URL to access the contents of the entry specified by the path
+ */
+ public URL getResourceURL(String path, BaseData hostData, int index) {
+ return internalGetResourceURL(path, hostData, 0, index);
+ }
+
+ private URL internalGetResourceURL(String path, BaseData hostData, long hostBundleID, int index) {
+ BundleEntry bundleEntry = getEntry(path);
+ if (bundleEntry == null)
+ return null;
+ if (hostData != null)
+ hostBundleID = hostData.getBundleID();
+ path = fixTrailingSlash(path, bundleEntry);
+ try {
+ //use the constant string for the protocol to prevent duplication
+ return secureAction.getURL(Constants.OSGI_RESOURCE_URL_PROTOCOL, Long.toString(hostBundleID) + BundleResourceHandler.BID_FWKID_SEPARATOR + Integer.toString(hostData.getAdaptor().hashCode()), index, path, new Handler(bundleEntry, hostData == null ? null : hostData.getAdaptor()));
+ } catch (MalformedURLException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the base file for this BundleFile
+ * @return the base file for this BundleFile
+ */
+ public File getBaseFile() {
+ return basefile;
+ }
+
+ void setMruIndex(int index) {
+ mruIndex = index;
+ }
+
+ int getMruIndex() {
+ return mruIndex;
+ }
+
+ /**
+ * Attempts to set the permissions of the file in a system dependent way.
+ * @param file the file to set the permissions on
+ */
+ public static void setPermissions(File file) {
+ String commandProp = FrameworkProperties.getProperty(PROP_SETPERMS_CMD);
+ if (commandProp == null)
+ commandProp = FrameworkProperties.getProperty(Constants.FRAMEWORK_EXECPERMISSION);
+ if (commandProp == null)
+ return;
+ String[] temp = ManifestElement.getArrayFromList(commandProp, " "); //$NON-NLS-1$
+ List<String> command = new ArrayList<String>(temp.length + 1);
+ boolean foundFullPath = false;
+ for (int i = 0; i < temp.length; i++) {
+ if ("[fullpath]".equals(temp[i]) || "${abspath}".equals(temp[i])) { //$NON-NLS-1$ //$NON-NLS-2$
+ command.add(file.getAbsolutePath());
+ foundFullPath = true;
+ } else
+ command.add(temp[i]);
+ }
+ if (!foundFullPath)
+ command.add(file.getAbsolutePath());
+ try {
+ Runtime.getRuntime().exec(command.toArray(new String[command.size()])).waitFor();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public String toString() {
+ return String.valueOf(basefile);
+ }
+
+ public static String fixTrailingSlash(String path, BundleEntry entry) {
+ if (path.length() == 0)
+ return "/"; //$NON-NLS-1$
+ if (path.charAt(0) != '/')
+ path = '/' + path;
+ String name = entry.getName();
+ if (name.length() == 0)
+ return path;
+ boolean pathSlash = path.charAt(path.length() - 1) == '/';
+ boolean entrySlash = name.length() > 0 && name.charAt(name.length() - 1) == '/';
+ if (entrySlash != pathSlash) {
+ if (entrySlash)
+ path = path + '/';
+ else
+ path = path.substring(0, path.length() - 1);
+ }
+ return path;
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/BundleFileWrapperChain.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/BundleFileWrapperChain.java
new file mode 100644
index 000000000..24e4730d5
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/BundleFileWrapperChain.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.baseadaptor.bundlefile;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Enumeration;
+import org.eclipse.osgi.baseadaptor.BaseData;
+import org.eclipse.osgi.baseadaptor.hooks.BundleFileWrapperFactoryHook;
+
+/**
+ * Used to chain the BundleFile objects returned from {@link BundleFileWrapperFactoryHook}.
+ * This class is useful for traversing the chain of wrapped bundle files.
+ */
+public class BundleFileWrapperChain extends BundleFile {
+ private final BundleFile wrapped;
+ private final BundleFileWrapperChain next;
+
+ public BundleFileWrapperChain(BundleFile wrapped, BundleFileWrapperChain next) {
+ this.wrapped = wrapped;
+ this.next = next;
+ }
+
+ public void close() throws IOException {
+ wrapped.close();
+ }
+
+ public boolean containsDir(String dir) {
+ return wrapped.containsDir(dir);
+ }
+
+ public BundleEntry getEntry(String path) {
+ return wrapped.getEntry(path);
+ }
+
+ public Enumeration<String> getEntryPaths(String path) {
+ return wrapped.getEntryPaths(path);
+ }
+
+ public File getFile(String path, boolean nativeCode) {
+ return wrapped.getFile(path, nativeCode);
+ }
+
+ public void open() throws IOException {
+ wrapped.open();
+ }
+
+ public File getBaseFile() {
+ return wrapped.getBaseFile();
+ }
+
+ public URL getResourceURL(String path, BaseData hostData, int index) {
+ return wrapped.getResourceURL(path, hostData, index);
+ }
+
+ public String toString() {
+ return wrapped.toString();
+ }
+
+ /**
+ * The BundleFile that is wrapped
+ * @return the BunldeFile that is wrapped
+ */
+ public BundleFile getWrapped() {
+ return wrapped;
+ }
+
+ /**
+ * The next WrapperBundleFile in the chain. A <code>null</code> value
+ * is returned if this is the end of the chain.
+ * @return the next WrapperBundleFile
+ */
+ public BundleFileWrapperChain getNext() {
+ return next;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/DirBundleFile.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/DirBundleFile.java
new file mode 100644
index 000000000..64c3f7191
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/DirBundleFile.java
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.baseadaptor.bundlefile;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.NoSuchElementException;
+import org.eclipse.osgi.internal.baseadaptor.AdaptorMsg;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * A BundleFile that uses a directory as its base file.
+ * @since 3.2
+ */
+public class DirBundleFile extends BundleFile {
+
+ /**
+ * Constructs a DirBundleFile
+ * @param basefile the base file
+ * @throws IOException
+ */
+ public DirBundleFile(File basefile) throws IOException {
+ super(basefile);
+ if (!BundleFile.secureAction.exists(basefile) || !BundleFile.secureAction.isDirectory(basefile)) {
+ throw new IOException(NLS.bind(AdaptorMsg.ADAPTOR_DIRECTORY_EXCEPTION, basefile));
+ }
+ }
+
+ public File getFile(String path, boolean nativeCode) {
+ boolean checkInBundle = path != null && path.indexOf("..") >= 0; //$NON-NLS-1$
+ File file = new File(basefile, path);
+ if (!BundleFile.secureAction.exists(file)) {
+ return null;
+ }
+ // must do an extra check to make sure file is within the bundle (bug 320546)
+ if (checkInBundle) {
+ try {
+ if (!BundleFile.secureAction.getCanonicalPath(file).startsWith(BundleFile.secureAction.getCanonicalPath(basefile)))
+ return null;
+ } catch (IOException e) {
+ return null;
+ }
+ }
+ return file;
+ }
+
+ public BundleEntry getEntry(String path) {
+ File filePath = getFile(path, false);
+ if (filePath == null)
+ return null;
+ return new FileBundleEntry(filePath, path);
+ }
+
+ public boolean containsDir(String dir) {
+ File dirPath = getFile(dir, false);
+ return dirPath != null && BundleFile.secureAction.isDirectory(dirPath);
+ }
+
+ public Enumeration<String> getEntryPaths(String path) {
+ if (path.length() > 0 && path.charAt(0) == '/')
+ path = path.substring(1);
+ final File pathFile = getFile(path, false);
+ if (pathFile == null || !BundleFile.secureAction.isDirectory(pathFile))
+ return null;
+ final String[] fileList = BundleFile.secureAction.list(pathFile);
+ if (fileList == null || fileList.length == 0)
+ return null;
+ final String dirPath = path.length() == 0 || path.charAt(path.length() - 1) == '/' ? path : path + '/';
+ return new Enumeration<String>() {
+ int cur = 0;
+
+ public boolean hasMoreElements() {
+ return fileList != null && cur < fileList.length;
+ }
+
+ public String nextElement() {
+ if (!hasMoreElements()) {
+ throw new NoSuchElementException();
+ }
+ java.io.File childFile = new java.io.File(pathFile, fileList[cur]);
+ StringBuffer sb = new StringBuffer(dirPath).append(fileList[cur++]);
+ if (BundleFile.secureAction.isDirectory(childFile)) {
+ sb.append("/"); //$NON-NLS-1$
+ }
+ return sb.toString();
+ }
+ };
+ }
+
+ public void close() {
+ // nothing to do.
+ }
+
+ public void open() {
+ // nothing to do.
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/DirZipBundleEntry.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/DirZipBundleEntry.java
new file mode 100644
index 000000000..6061ee053
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/DirZipBundleEntry.java
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.baseadaptor.bundlefile;
+
+import java.io.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * Represents a directory entry in a ZipBundleFile. This object is used to
+ * reference a directory entry in a ZipBundleFile when the directory entries are
+ * not included in the zip file.
+ * @since 3.2
+ */
+public class DirZipBundleEntry extends BundleEntry {
+
+ /**
+ * ZipBundleFile for this entry.
+ */
+ private ZipBundleFile bundleFile;
+ /**
+ * The name for this entry
+ */
+ String name;
+
+ public DirZipBundleEntry(ZipBundleFile bundleFile, String name) {
+ this.name = (name.length() > 0 && name.charAt(0) == '/') ? name.substring(1) : name;
+ this.bundleFile = bundleFile;
+ }
+
+ /**
+ * @throws IOException
+ */
+ public InputStream getInputStream() throws IOException {
+ return new ByteArrayInputStream(new byte[0]);
+ }
+
+ public long getSize() {
+ return 0;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public long getTime() {
+ return 0;
+ }
+
+ @SuppressWarnings("deprecation")
+ public URL getLocalURL() {
+ try {
+ return new URL("jar:" + bundleFile.basefile.toURL() + "!/" + name); //$NON-NLS-1$ //$NON-NLS-2$
+ } catch (MalformedURLException e) {
+ //This can not happen, unless the jar protocol is not supported.
+ return null;
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ public URL getFileURL() {
+ try {
+ return bundleFile.extractDirectory(name).toURL();
+ } catch (MalformedURLException e) {
+ // this cannot happen.
+ return null;
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/FileBundleEntry.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/FileBundleEntry.java
new file mode 100644
index 000000000..21a7b6c88
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/FileBundleEntry.java
@@ -0,0 +1,100 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.baseadaptor.bundlefile;
+
+import java.io.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * A BundleEntry represented by a File object. The FileBundleEntry class is
+ * used for bundles that are installed as extracted zips on a file system.
+ * @since 3.2
+ */
+public class FileBundleEntry extends BundleEntry {
+ /**
+ * File for this entry.
+ */
+ private final File file;
+ /**
+ * The name for this entry
+ */
+ private final String name;
+
+ /**
+ * Constructs the BundleEntry using a File.
+ * @param file BundleFile object this entry is a member of
+ * @param name the name of this BundleEntry
+ */
+ FileBundleEntry(File file, String name) {
+ this.file = file;
+ boolean endsInSlash = name.length() > 0 && name.charAt(name.length() - 1) == '/';
+ if (BundleFile.secureAction.isDirectory(file)) {
+ if (!endsInSlash)
+ name += '/';
+ } else if (endsInSlash)
+ name = name.substring(0, name.length() - 1);
+ this.name = name;
+ }
+
+ /**
+ * Return an InputStream for the entry.
+ *
+ * @return InputStream for the entry
+ * @exception java.io.IOException
+ */
+ public InputStream getInputStream() throws IOException {
+ return BundleFile.secureAction.getFileInputStream(file);
+ }
+
+ /**
+ * Return size of the uncompressed entry.
+ *
+ * @return size of entry
+ */
+ public long getSize() {
+ return BundleFile.secureAction.length(file);
+ }
+
+ /**
+ * Return name of the entry.
+ *
+ * @return name of entry
+ */
+ public String getName() {
+ return (name);
+ }
+
+ /**
+ * Get the modification time for this BundleEntry.
+ * <p>If the modification time has not been set,
+ * this method will return <tt>-1</tt>.
+ *
+ * @return last modification time.
+ */
+ public long getTime() {
+ return BundleFile.secureAction.lastModified(file);
+ }
+
+ public URL getLocalURL() {
+ return getFileURL();
+ }
+
+ @SuppressWarnings("deprecation")
+ public URL getFileURL() {
+ try {
+ return file.toURL();
+ } catch (MalformedURLException e) {
+ return null;
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/MRUBundleFileList.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/MRUBundleFileList.java
new file mode 100644
index 000000000..8c59890a2
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/MRUBundleFileList.java
@@ -0,0 +1,239 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.baseadaptor.bundlefile;
+
+import java.io.IOException;
+import java.util.Map;
+import org.eclipse.osgi.framework.eventmgr.*;
+
+/**
+ * A simple/quick/small implementation of an MRU (Most Recently Used) list to keep
+ * track of open BundleFiles. The MRU will use the file limit specified by the property
+ * &quot;osgi.bundlefile.limit&quot; by default unless the MRU is constructed with a specific
+ * file limit.
+ * @since 3.2
+ */
+public class MRUBundleFileList implements EventDispatcher<Object, Object, BundleFile> {
+ private static final String PROP_FILE_LIMIT = "osgi.bundlefile.limit"; //$NON-NLS-1$
+ private static final int MIN = 10;
+ private static final int PROP_FILE_LIMIT_VALUE;
+ private static final ThreadLocal<BundleFile> closingBundleFile = new ThreadLocal<BundleFile>();
+ static {
+ int propValue = 100; // enable to 100 open files by default
+ try {
+ String prop = BundleFile.secureAction.getProperty(PROP_FILE_LIMIT);
+ if (prop != null)
+ propValue = Integer.parseInt(prop);
+ } catch (NumberFormatException e) {
+ //MRU will be disabled
+ }
+ PROP_FILE_LIMIT_VALUE = propValue;
+ }
+ // list of open bundle files
+ final private BundleFile[] bundleFileList;
+ // list of open bundle files use stamps
+ final private long[] useStampList;
+ // the limit of open files to allow before least used bundle file is closed
+ final private int fileLimit; // value < MIN will disable MRU
+ private EventManager bundleFileCloserManager = null;
+ final private Map<Object, Object> bundleFileCloser;
+ // the current number of open bundle files
+ private int numOpen = 0;
+ // the current use stamp
+ private long curUseStamp = 0;
+ // used to work around bug 275166
+ private boolean firstDispatch = true;
+
+ public MRUBundleFileList() {
+ this(PROP_FILE_LIMIT_VALUE);
+ }
+
+ public MRUBundleFileList(int fileLimit) {
+ // only enable the MRU if the initFileLimit is > MIN
+ this.fileLimit = fileLimit;
+ if (fileLimit >= MIN) {
+ this.bundleFileList = new BundleFile[fileLimit];
+ this.useStampList = new long[fileLimit];
+ this.bundleFileCloser = new CopyOnWriteIdentityMap<Object, Object>();
+ this.bundleFileCloser.put(this, this);
+ } else {
+ this.bundleFileList = null;
+ this.useStampList = null;
+ this.bundleFileCloser = null;
+ }
+ }
+
+ /**
+ * Adds a BundleFile which is about to be opened to the MRU list. If
+ * the number of open BundleFiles == the fileLimit then the least
+ * recently used BundleFile is closed.
+ * @param bundleFile the bundle file about to be opened.
+ */
+ public void add(BundleFile bundleFile) {
+ if (fileLimit < MIN)
+ return; // MRU is disabled
+ BundleFile toRemove = null;
+ EventManager manager = null;
+ synchronized (this) {
+ if (bundleFile.getMruIndex() >= 0)
+ return; // do nothing; someone is trying add a bundleFile that is already in an MRU list
+ int index = 0; // default to the first slot
+ if (numOpen < fileLimit) {
+ // numOpen does not exceed the fileLimit
+ // find the first null slot to use in the MRU
+ for (int i = 0; i < fileLimit; i++)
+ if (bundleFileList[i] == null) {
+ index = i;
+ break;
+ }
+ } else {
+ // numOpen has reached the fileLimit
+ // find the least recently used bundleFile and close it
+ // and use it slot for the new bundleFile to be opened.
+ index = 0;
+ for (int i = 1; i < fileLimit; i++)
+ if (useStampList[i] < useStampList[index])
+ index = i;
+ toRemove = bundleFileList[index];
+ if (toRemove.getMruIndex() != index)
+ throw new IllegalStateException("The BundleFile has the incorrect mru index: " + index + " != " + toRemove.getMruIndex()); //$NON-NLS-1$//$NON-NLS-2$
+ removeInternal(toRemove);
+ }
+ // found an index to place to bundleFile to be opened
+ bundleFileList[index] = bundleFile;
+ bundleFile.setMruIndex(index);
+ incUseStamp(index);
+ numOpen++;
+ if (toRemove != null) {
+ if (bundleFileCloserManager == null)
+ bundleFileCloserManager = new EventManager("Bundle File Closer"); //$NON-NLS-1$
+ manager = bundleFileCloserManager;
+ }
+
+ }
+ // must not close the toRemove bundle file while holding the lock of another bundle file (bug 161976)
+ // This queues the bundle file for close asynchronously.
+ closeBundleFile(toRemove, manager);
+ }
+
+ /**
+ * Removes a bundle file which is about to be closed
+ * @param bundleFile the bundle file about to be closed
+ * @return true if the bundleFile existed in the MRU; false otherwise
+ */
+ public boolean remove(BundleFile bundleFile) {
+ if (fileLimit < MIN)
+ return false; // MRU is disabled
+ synchronized (this) {
+ int index = bundleFile.getMruIndex();
+ if ((index >= 0 && index < fileLimit) && bundleFileList[index] == bundleFile) {
+ removeInternal(bundleFile);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // must be called while synchronizing "this"
+ private void removeInternal(BundleFile bundleFile) {
+ int index = bundleFile.getMruIndex();
+ bundleFile.setMruIndex(-1);
+ bundleFileList[index] = null;
+ useStampList[index] = -1;
+ numOpen--;
+ }
+
+ /**
+ * Increments the use stamp of a bundle file
+ * @param bundleFile the bundle file to increment the use stamp for
+ */
+ public void use(BundleFile bundleFile) {
+ if (fileLimit < MIN)
+ return; // MRU is disabled
+ synchronized (this) {
+ int index = bundleFile.getMruIndex();
+ if ((index >= 0 && index < fileLimit) && bundleFileList[index] == bundleFile)
+ incUseStamp(index);
+ }
+ }
+
+ // must be called while synchronizing "this"
+ private void incUseStamp(int index) {
+ if (curUseStamp == Long.MAX_VALUE) {
+ // we hit the curUseStamp max better reset all the stamps
+ for (int i = 0; i < fileLimit; i++)
+ useStampList[i] = 0;
+ curUseStamp = 0;
+ }
+ useStampList[index] = ++curUseStamp;
+ }
+
+ public final void dispatchEvent(Object eventListener, Object listenerObject, int eventAction, BundleFile eventObject) {
+ if (firstDispatch) {
+ // used to work around bug 275166; we don't want to leak the TCCL in this thread.
+ Thread.currentThread().setContextClassLoader(null);
+ firstDispatch = false;
+ }
+ try {
+ closingBundleFile.set(eventObject);
+ eventObject.close();
+ } catch (IOException e) {
+ // TODO should log ??
+ } finally {
+ closingBundleFile.set(null);
+ }
+ }
+
+ private void closeBundleFile(BundleFile toRemove, EventManager manager) {
+ if (toRemove == null)
+ return;
+ try {
+ /* queue to hold set of listeners */
+ ListenerQueue<Object, Object, BundleFile> queue = new ListenerQueue<Object, Object, BundleFile>(manager);
+ /* add bundle file closer to the queue */
+ queue.queueListeners(bundleFileCloser.entrySet(), this);
+ /* dispatch event to set of listeners */
+ queue.dispatchEventAsynchronous(0, toRemove);
+ } catch (Throwable t) {
+ // we cannot propagate exceptions out of this method
+ // failing to queue a bundle close should not cause an error (bug 283797)
+ // TODO should consider logging
+ }
+ }
+
+ /**
+ * Closes the bundle file closer thread for the MRU list
+ */
+ public void shutdown() {
+ synchronized (this) {
+ if (bundleFileCloserManager != null)
+ bundleFileCloserManager.close();
+ bundleFileCloserManager = null;
+ }
+ }
+
+ /**
+ * Returns true if this MRUBundleFileList is currently closing the specified bundle file on the current thread.
+ * @param bundleFile the bundle file
+ * @return true if the bundle file is being closed on the current thread
+ */
+ public boolean isClosing(BundleFile bundleFile) {
+ if (fileLimit < MIN)
+ return false; // MRU is disabled
+ // check the thread local variable
+ return closingBundleFile.get() == bundleFile;
+ }
+
+ public boolean isEnabled() {
+ return fileLimit >= MIN;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/NestedDirBundleFile.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/NestedDirBundleFile.java
new file mode 100644
index 000000000..88514c2b9
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/NestedDirBundleFile.java
@@ -0,0 +1,99 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.baseadaptor.bundlefile;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Enumeration;
+
+/**
+ * A NestedDirBundleFile uses another BundleFile as its source but
+ * accesses all of its resources relative to a nested directory within
+ * the other BundleFile object. This is used to support zipped bundles
+ * that use a Bundle-ClassPath with an nested directory specified.
+ * <p>
+ * For Example:
+ * <pre>
+ * Bundle-ClassPath: nested.jar,nesteddir/
+ * </pre>
+ * @since 3.2
+ */
+public class NestedDirBundleFile extends BundleFile {
+ private final BundleFile baseBundleFile;
+ private final String cp;
+
+ /**
+ * Constructs a NestedDirBundleFile
+ * @param baseBundlefile the base bundle file
+ * @param cp
+ */
+ public NestedDirBundleFile(BundleFile baseBundlefile, String cp) {
+ super(baseBundlefile.getBaseFile());
+ this.baseBundleFile = baseBundlefile;
+ if (cp.charAt(cp.length() - 1) != '/') {
+ cp = cp + '/';
+ }
+ this.cp = cp;
+ }
+
+ public void close() {
+ // do nothing.
+ }
+
+ public BundleEntry getEntry(String path) {
+ return baseBundleFile.getEntry(prependNestedDir(path));
+ }
+
+ public boolean containsDir(String dir) {
+ if (dir == null)
+ return false;
+
+ return baseBundleFile.containsDir(prependNestedDir(dir));
+ }
+
+ private String prependNestedDir(String path) {
+ if (path.length() > 0 && path.charAt(0) == '/')
+ path = path.substring(1);
+ return new StringBuffer(cp).append(path).toString();
+ }
+
+ public Enumeration<String> getEntryPaths(String path) {
+ final Enumeration<String> basePaths = baseBundleFile.getEntryPaths(prependNestedDir(path));
+ final int cpLength = cp.length();
+ if (basePaths == null)
+ return null;
+ return new Enumeration<String>() {
+
+ public boolean hasMoreElements() {
+ return basePaths.hasMoreElements();
+ }
+
+ public String nextElement() {
+ String next = basePaths.nextElement();
+ return next.substring(cpLength);
+ }
+ };
+ }
+
+ public File getFile(String entry, boolean nativeCode) {
+ // getFile is only valid if this is a root bundle file.
+ // TODO to catch bugs we probably should throw new UnsupportedOperationException()
+ return null;
+ }
+
+ /**
+ * @throws IOException
+ */
+ public void open() throws IOException {
+ // do nothing
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/ZipBundleEntry.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/ZipBundleEntry.java
new file mode 100644
index 000000000..06a46aa9b
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/ZipBundleEntry.java
@@ -0,0 +1,172 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Rob Harrop - SpringSource Inc. (bug 253942)
+ *******************************************************************************/
+package org.eclipse.osgi.baseadaptor.bundlefile;
+
+import java.io.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.zip.ZipEntry;
+
+/**
+ * A BundleEntry represented by a ZipEntry in a ZipFile. The ZipBundleEntry
+ * class is used for bundles that are installed as a ZipFile on a file system.
+ * @since 3.2
+ */
+public class ZipBundleEntry extends BundleEntry {
+ /**
+ * ZipEntry for this entry.
+ */
+ protected final ZipEntry zipEntry;
+
+ /**
+ * The BundleFile for this entry.
+ */
+ protected final ZipBundleFile bundleFile;
+
+ /**
+ * Constructs the BundleEntry using a ZipEntry.
+ * @param bundleFile BundleFile object this entry is a member of
+ * @param zipEntry ZipEntry object of this entry
+ */
+ ZipBundleEntry(ZipEntry zipEntry, ZipBundleFile bundleFile) {
+ this.zipEntry = zipEntry;
+ this.bundleFile = bundleFile;
+ }
+
+ /**
+ * Return an InputStream for the entry.
+ *
+ * @return InputStream for the entry
+ * @exception java.io.IOException
+ */
+ public InputStream getInputStream() throws IOException {
+ ZipBundleFile zipBundleFile = bundleFile;
+
+ if (!zipBundleFile.isMruEnabled())
+ return bundleFile.getZipFile().getInputStream(zipEntry);
+
+ zipBundleFile.incrementReference();
+ InputStream result = null;
+ try {
+ return result = new ZipBundleEntryInputStream(zipBundleFile.getZipFile().getInputStream(zipEntry));
+ } finally {
+ if (result == null)
+ // an exception occurred; decrement the reference
+ zipBundleFile.decrementReference();
+ }
+ }
+
+ /**
+ * Return size of the uncompressed entry.
+ *
+ * @return size of entry
+ */
+ public long getSize() {
+ return zipEntry.getSize();
+ }
+
+ /**
+ * Return name of the entry.
+ *
+ * @return name of entry
+ */
+ public String getName() {
+ return zipEntry.getName();
+ }
+
+ /**
+ * Get the modification time for this BundleEntry.
+ * <p>If the modification time has not been set,
+ * this method will return <tt>-1</tt>.
+ *
+ * @return last modification time.
+ */
+ public long getTime() {
+ return zipEntry.getTime();
+ }
+
+ @SuppressWarnings("deprecation")
+ public URL getLocalURL() {
+ try {
+ return new URL("jar:" + bundleFile.basefile.toURL() + "!/" + zipEntry.getName()); //$NON-NLS-1$//$NON-NLS-2$
+ } catch (MalformedURLException e) {
+ //This can not happen.
+ return null;
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ public URL getFileURL() {
+ try {
+ File file = bundleFile.getFile(zipEntry.getName(), false);
+ if (file != null)
+ return file.toURL();
+ } catch (MalformedURLException e) {
+ //This can not happen.
+ }
+ return null;
+ }
+
+ private class ZipBundleEntryInputStream extends InputStream {
+ private final InputStream stream;
+ private boolean closed = false;
+
+ public ZipBundleEntryInputStream(InputStream stream) {
+ this.stream = stream;
+ }
+
+ public int available() throws IOException {
+ return stream.available();
+ }
+
+ public void close() throws IOException {
+ try {
+ stream.close();
+ } finally {
+ synchronized (this) {
+ if (closed)
+ return;
+ closed = true;
+ }
+ bundleFile.decrementReference();
+ }
+ }
+
+ public void mark(int var0) {
+ stream.mark(var0);
+ }
+
+ public boolean markSupported() {
+ return stream.markSupported();
+ }
+
+ public int read() throws IOException {
+ return stream.read();
+ }
+
+ public int read(byte[] var0, int var1, int var2) throws IOException {
+ return stream.read(var0, var1, var2);
+ }
+
+ public int read(byte[] var0) throws IOException {
+ return stream.read(var0);
+ }
+
+ public void reset() throws IOException {
+ stream.reset();
+ }
+
+ public long skip(long var0) throws IOException {
+ return stream.skip(var0);
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/ZipBundleFile.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/ZipBundleFile.java
new file mode 100644
index 000000000..9ce5c789d
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/bundlefile/ZipBundleFile.java
@@ -0,0 +1,357 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Rob Harrop - SpringSource Inc. (bug 253942)
+ *******************************************************************************/
+
+package org.eclipse.osgi.baseadaptor.bundlefile;
+
+import java.io.*;
+import java.util.*;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import org.eclipse.osgi.baseadaptor.BaseData;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.internal.baseadaptor.AdaptorMsg;
+import org.eclipse.osgi.internal.baseadaptor.AdaptorUtil;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.FrameworkEvent;
+
+/**
+ * A BundleFile that uses a ZipFile as it base file.
+ * @since 3.2
+ */
+public class ZipBundleFile extends BundleFile {
+
+ private final MRUBundleFileList mruList;
+ /**
+ * The bundle data
+ */
+ protected BaseData bundledata;
+ /**
+ * The zip file
+ */
+ protected volatile ZipFile zipFile;
+ /**
+ * The closed flag
+ */
+ protected volatile boolean closed = true;
+
+ private int referenceCount = 0;
+
+ /**
+ * Constructs a ZipBundle File
+ * @param basefile the base file
+ * @param bundledata the bundle data
+ * @throws IOException
+ */
+ public ZipBundleFile(File basefile, BaseData bundledata) throws IOException {
+ this(basefile, bundledata, null);
+ }
+
+ public ZipBundleFile(File basefile, BaseData bundledata, MRUBundleFileList mruList) throws IOException {
+ super(basefile);
+ if (!BundleFile.secureAction.exists(basefile))
+ throw new IOException(NLS.bind(AdaptorMsg.ADAPTER_FILEEXIST_EXCEPTION, basefile));
+ this.bundledata = bundledata;
+ this.closed = true;
+ this.mruList = mruList;
+ }
+
+ /**
+ * Checks if the zip file is open
+ * @return true if the zip file is open
+ */
+ protected boolean checkedOpen() {
+ try {
+ return getZipFile() != null;
+ } catch (IOException e) {
+ if (bundledata != null)
+ bundledata.getAdaptor().getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, bundledata.getBundle(), e);
+ return false;
+ }
+ }
+
+ /**
+ * Opens the ZipFile for this bundle file
+ * @return an open ZipFile for this bundle file
+ * @throws IOException
+ */
+ protected ZipFile basicOpen() throws IOException {
+ return BundleFile.secureAction.getZipFile(this.basefile);
+ }
+
+ /**
+ * Returns an open ZipFile for this bundle file. If an open
+ * ZipFile does not exist then a new one is created and
+ * returned.
+ * @return an open ZipFile for this bundle
+ * @throws IOException
+ */
+ protected synchronized ZipFile getZipFile() throws IOException {
+ if (closed) {
+ mruListAdd();
+ zipFile = basicOpen();
+ closed = false;
+ } else
+ mruListUse();
+ return zipFile;
+ }
+
+ /**
+ * Returns a ZipEntry for the bundle file. Must be called while synchronizing on this object.
+ * This method does not ensure that the ZipFile is opened. Callers may need to call getZipfile() prior to calling this
+ * method.
+ * @param path the path to an entry
+ * @return a ZipEntry or null if the entry does not exist
+ */
+ protected ZipEntry getZipEntry(String path) {
+ if (path.length() > 0 && path.charAt(0) == '/')
+ path = path.substring(1);
+ ZipEntry entry = zipFile.getEntry(path);
+ if (entry != null && entry.getSize() == 0 && !entry.isDirectory()) {
+ // work around the directory bug see bug 83542
+ ZipEntry dirEntry = zipFile.getEntry(path + '/');
+ if (dirEntry != null)
+ entry = dirEntry;
+ }
+ return entry;
+ }
+
+ /**
+ * Extracts a directory and all sub content to disk
+ * @param dirName the directory name to extract
+ * @return the File used to extract the content to. A value
+ * of <code>null</code> is returned if the directory to extract does
+ * not exist or if content extraction is not supported.
+ */
+ protected synchronized File extractDirectory(String dirName) {
+ if (!checkedOpen())
+ return null;
+ Enumeration<? extends ZipEntry> entries = zipFile.entries();
+ while (entries.hasMoreElements()) {
+ String entryPath = entries.nextElement().getName();
+ if (entryPath.startsWith(dirName) && !entryPath.endsWith("/")) //$NON-NLS-1$
+ getFile(entryPath, false);
+ }
+ return getExtractFile(dirName);
+ }
+
+ protected File getExtractFile(String entryName) {
+ if (bundledata == null)
+ return null;
+ String path = ".cp"; /* put all these entries in this subdir *///$NON-NLS-1$
+ String name = entryName.replace('/', File.separatorChar);
+ if ((name.length() > 1) && (name.charAt(0) == File.separatorChar)) /* if name has a leading slash */
+ path = path.concat(name);
+ else
+ path = path + File.separator + name;
+ return bundledata.getExtractFile(path);
+ }
+
+ public synchronized File getFile(String entry, boolean nativeCode) {
+ if (!checkedOpen())
+ return null;
+ ZipEntry zipEntry = getZipEntry(entry);
+ if (zipEntry == null)
+ return null;
+
+ try {
+ File nested = getExtractFile(zipEntry.getName());
+ if (nested != null) {
+ if (nested.exists()) {
+ /* the entry is already cached */
+ if (Debug.DEBUG_GENERAL)
+ Debug.println("File already present: " + nested.getPath()); //$NON-NLS-1$
+ if (nested.isDirectory())
+ // must ensure the complete directory is extracted (bug 182585)
+ extractDirectory(zipEntry.getName());
+ } else {
+ if (zipEntry.getName().endsWith("/")) { //$NON-NLS-1$
+ if (!nested.mkdirs()) {
+ if (Debug.DEBUG_GENERAL)
+ Debug.println("Unable to create directory: " + nested.getPath()); //$NON-NLS-1$
+ throw new IOException(NLS.bind(AdaptorMsg.ADAPTOR_DIRECTORY_CREATE_EXCEPTION, nested.getAbsolutePath()));
+ }
+ extractDirectory(zipEntry.getName());
+ } else {
+ InputStream in = zipFile.getInputStream(zipEntry);
+ if (in == null)
+ return null;
+ /* the entry has not been cached */
+ if (Debug.DEBUG_GENERAL)
+ Debug.println("Creating file: " + nested.getPath()); //$NON-NLS-1$
+ /* create the necessary directories */
+ File dir = new File(nested.getParent());
+ if (!dir.exists() && !dir.mkdirs()) {
+ if (Debug.DEBUG_GENERAL)
+ Debug.println("Unable to create directory: " + dir.getPath()); //$NON-NLS-1$
+ throw new IOException(NLS.bind(AdaptorMsg.ADAPTOR_DIRECTORY_CREATE_EXCEPTION, dir.getAbsolutePath()));
+ }
+ /* copy the entry to the cache */
+ AdaptorUtil.readFile(in, nested);
+ if (nativeCode)
+ setPermissions(nested);
+ }
+ }
+
+ return nested;
+ }
+ } catch (IOException e) {
+ if (Debug.DEBUG_GENERAL)
+ Debug.printStackTrace(e);
+ }
+ return null;
+ }
+
+ public synchronized boolean containsDir(String dir) {
+ if (!checkedOpen())
+ return false;
+ if (dir == null)
+ return false;
+
+ if (dir.length() == 0)
+ return true;
+
+ if (dir.charAt(0) == '/') {
+ if (dir.length() == 1)
+ return true;
+ dir = dir.substring(1);
+ }
+
+ if (dir.length() > 0 && dir.charAt(dir.length() - 1) != '/')
+ dir = dir + '/';
+
+ Enumeration<? extends ZipEntry> entries = zipFile.entries();
+ ZipEntry zipEntry;
+ String entryPath;
+ while (entries.hasMoreElements()) {
+ zipEntry = entries.nextElement();
+ entryPath = zipEntry.getName();
+ if (entryPath.startsWith(dir)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public synchronized BundleEntry getEntry(String path) {
+ if (!checkedOpen())
+ return null;
+ ZipEntry zipEntry = getZipEntry(path);
+ if (zipEntry == null) {
+ if (path.length() == 0 || path.charAt(path.length() - 1) == '/') {
+ // this is a directory request lets see if any entries exist in this directory
+ if (containsDir(path))
+ return new DirZipBundleEntry(this, path);
+ }
+ return null;
+ }
+
+ return new ZipBundleEntry(zipEntry, this);
+
+ }
+
+ public synchronized Enumeration<String> getEntryPaths(String path) {
+ if (!checkedOpen())
+ return null;
+ if (path == null)
+ throw new NullPointerException();
+
+ if (path.length() > 0 && path.charAt(0) == '/')
+ path = path.substring(1);
+ if (path.length() > 0 && path.charAt(path.length() - 1) != '/')
+ path = new StringBuffer(path).append("/").toString(); //$NON-NLS-1$
+
+ List<String> vEntries = new ArrayList<String>();
+ Enumeration<? extends ZipEntry> entries = zipFile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry zipEntry = entries.nextElement();
+ String entryPath = zipEntry.getName();
+ if (entryPath.startsWith(path)) {
+ if (path.length() < entryPath.length()) {
+ if (entryPath.lastIndexOf('/') < path.length()) {
+ vEntries.add(entryPath);
+ } else {
+ entryPath = entryPath.substring(path.length());
+ int slash = entryPath.indexOf('/');
+ entryPath = path + entryPath.substring(0, slash + 1);
+ if (!vEntries.contains(entryPath))
+ vEntries.add(entryPath);
+ }
+ }
+ }
+ }
+ return vEntries.size() == 0 ? null : Collections.enumeration(vEntries);
+ }
+
+ public synchronized void close() throws IOException {
+ if (!closed) {
+ if (referenceCount > 0 && isMruListClosing()) {
+ // there are some opened streams to this BundleFile still;
+ // wait for them all to close because this is being closed by the MRUBundleFileList
+ try {
+ wait(1000); // timeout after 1 second
+ } catch (InterruptedException e) {
+ // do nothing for now ...
+ }
+ if (referenceCount != 0 || closed)
+ // either another thread closed the bundle file or we timed waiting for all the reference inputstreams to close
+ // If the referenceCount did not reach zero then this bundle file will remain open until the
+ // bundle file is closed explicitly (i.e. bundle is updated/uninstalled or framework is shutdown)
+ return;
+
+ }
+ closed = true;
+ zipFile.close();
+ mruListRemove();
+ }
+ }
+
+ private boolean isMruListClosing() {
+ return this.mruList != null && this.mruList.isClosing(this);
+ }
+
+ boolean isMruEnabled() {
+ return this.mruList != null && this.mruList.isEnabled();
+ }
+
+ private void mruListRemove() {
+ if (this.mruList != null) {
+ this.mruList.remove(this);
+ }
+ }
+
+ private void mruListUse() {
+ if (this.mruList != null) {
+ mruList.use(this);
+ }
+ }
+
+ private void mruListAdd() {
+ if (this.mruList != null) {
+ mruList.add(this);
+ }
+ }
+
+ public void open() {
+ //do nothing
+ }
+
+ synchronized void incrementReference() {
+ referenceCount += 1;
+ }
+
+ synchronized void decrementReference() {
+ referenceCount = Math.max(0, referenceCount - 1);
+ // only notify if the referenceCount is zero.
+ if (referenceCount == 0)
+ notify();
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/hooks/AdaptorHook.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/hooks/AdaptorHook.java
new file mode 100644
index 000000000..6c7bcdedc
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/hooks/AdaptorHook.java
@@ -0,0 +1,100 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.baseadaptor.hooks;
+
+import java.io.IOException;
+import java.net.URLConnection;
+import java.util.Properties;
+import org.eclipse.osgi.baseadaptor.BaseAdaptor;
+import org.eclipse.osgi.baseadaptor.HookRegistry;
+import org.eclipse.osgi.framework.adaptor.EventPublisher;
+import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor;
+import org.eclipse.osgi.framework.log.FrameworkLog;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+
+/**
+ * An AdaptorHook hooks into the <code>BaseAdaptor</code> class.
+ * @see BaseAdaptor
+ * @see HookRegistry#getAdaptorHooks()
+ * @see HookRegistry#addAdaptorHook(AdaptorHook)
+ * @since 3.2
+ */
+public interface AdaptorHook {
+ /**
+ * Gets called by the adaptor during {@link FrameworkAdaptor#initialize(EventPublisher)}.
+ * This method allows an adaptor hook to save the adaptor object for later.
+ * @param adaptor the adaptor object associated with this AdaptorHook.
+ */
+ public void initialize(BaseAdaptor adaptor);
+
+ /**
+ * Gets called by the adaptor during {@link FrameworkAdaptor#frameworkStart(BundleContext)}.
+ * This method allows an adaptor hook to execute code when the framework is starting
+ * (e.g. to register services).
+ * @param context the system bundle context
+ * @throws BundleException if an error occurs
+ */
+ public void frameworkStart(BundleContext context) throws BundleException;
+
+ /**
+ * Gets called by the adaptor during {@link FrameworkAdaptor#frameworkStop(BundleContext)}.
+ * This method allows an adaptor hook to execute code when the framework is stopped
+ * (e.g. to unregister services).
+ * @param context the system bundle context
+ * @throws BundleException if an error occurs.
+ */
+ public void frameworkStop(BundleContext context) throws BundleException;
+
+ /**
+ * Gets called by the adaptor during {@link FrameworkAdaptor#frameworkStopping(BundleContext)}.
+ * This method allows an adaptor hook to execute code when the framework is about to start
+ * the shutdown process.
+ * @param context the system bundle context
+ */
+ public void frameworkStopping(BundleContext context);
+
+ /**
+ * Gets called by the adaptor during {@link FrameworkAdaptor#getProperties()}.
+ * This method allows an adaptor hook to add property values to the adaptor
+ * properties object.
+ * @param properties the adaptor properties object.
+ */
+ public void addProperties(Properties properties);
+
+ /**
+ * Gets called by the adaptor during {@link FrameworkAdaptor#mapLocationToURLConnection(String)}.
+ * The adaptor will call this method for each configured adaptor hook until one
+ * adaptor hook returns a non-null value. If no adaptor hook returns a non-null value
+ * then the adaptor will perform the default behavior.
+ * @param location a bundle location string to be converted to a URLConnection
+ * @return the URLConnection converted from the bundle location or null.
+ * @throws IOException if an error occured creating the URLConnection
+ */
+ public URLConnection mapLocationToURLConnection(String location) throws IOException;
+
+ /**
+ * Gets called by the adaptor during {@link FrameworkAdaptor#handleRuntimeError(Throwable)}.
+ * The adaptor will call this method for each configured adaptor hook.
+ * @param error the unexpected error that occured.
+ */
+ public void handleRuntimeError(Throwable error);
+
+ /**
+ * Gets called by the adaptor during {@link FrameworkAdaptor#getFrameworkLog()}.
+ * The adaptor will call this method for each configured adaptor hook until one
+ * adaptor hook returns a non-null value. If no adaptor hook returns a non-null value
+ * then the adaptor will return null.
+ * @return a FrameworkLog object or null.
+ */
+ public FrameworkLog createFrameworkLog();
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/hooks/BundleFileFactoryHook.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/hooks/BundleFileFactoryHook.java
new file mode 100644
index 000000000..0317f1483
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/hooks/BundleFileFactoryHook.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.baseadaptor.hooks;
+
+import java.io.IOException;
+import org.eclipse.osgi.baseadaptor.BaseAdaptor;
+import org.eclipse.osgi.baseadaptor.BaseData;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile;
+
+/**
+ * A factory that creates bundle file objects.
+ * @see BaseAdaptor#createBundleFile(Object, BaseData)
+ * @since 3.2
+ */
+public interface BundleFileFactoryHook {
+ /**
+ * Creates a bundle file for the given content and base data.
+ * @param content The object which contains the content of a bundle file.
+ * @param data The base data associated with the content
+ * @param base true if the content is for the base bundle (not an inner jar, directory etc.)
+ * @return a new bundle file for the specified content, or null if this factory cannot
+ * create a bundle file for the specified content.
+ * @throws IOException if an IO error occurs
+ */
+ BundleFile createBundleFile(Object content, BaseData data, boolean base) throws IOException;
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/hooks/BundleFileWrapperFactoryHook.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/hooks/BundleFileWrapperFactoryHook.java
new file mode 100644
index 000000000..2c37adb46
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/hooks/BundleFileWrapperFactoryHook.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.baseadaptor.hooks;
+
+import java.io.IOException;
+import org.eclipse.osgi.baseadaptor.BaseAdaptor;
+import org.eclipse.osgi.baseadaptor.BaseData;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile;
+
+/**
+ * A factory that wraps bundle file objects.
+ * @see BaseAdaptor#createBundleFile(Object, BaseData)
+ * @since 3.2
+ */
+public interface BundleFileWrapperFactoryHook {
+ /**
+ * Wraps a bundle file for the given content and base data. If the
+ * specified bundle file should not be wrapped then null is returned
+ * @param bundleFile the bundle file to be wrapped
+ * @param content The object which contains the content of a bundle file.
+ * @param data The base data associated with the content
+ * @param base true if the content is for the base bundle (not an inner jar, directory etc.)
+ * @return a wrapped bundle file for the specified content, or null if the bundle content
+ * is not wrapped.
+ * @throws IOException if an IO error occurs
+ */
+ BundleFile wrapBundleFile(BundleFile bundleFile, Object content, BaseData data, boolean base) throws IOException;
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/hooks/ClassLoadingHook.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/hooks/ClassLoadingHook.java
new file mode 100644
index 000000000..59e36eac4
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/hooks/ClassLoadingHook.java
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.baseadaptor.hooks;
+
+import java.security.ProtectionDomain;
+import java.util.ArrayList;
+import org.eclipse.osgi.baseadaptor.BaseData;
+import org.eclipse.osgi.baseadaptor.HookRegistry;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
+import org.eclipse.osgi.baseadaptor.loader.*;
+import org.eclipse.osgi.framework.adaptor.*;
+
+/**
+ * A ClassLoadingHook hooks into the <code>ClasspathManager</code> class.
+ * @see ClasspathManager
+ * @see HookRegistry#getClassLoadingHooks()
+ * @see HookRegistry#addClassLoadingHook(ClassLoadingHook)
+ * @since 3.2
+ */
+public interface ClassLoadingHook {
+ /**
+ * Gets called by a classpath manager before defining a class. This method allows a class loading hook
+ * to process the bytes of a class that is about to be defined.
+ * @param name the name of the class being defined
+ * @param classbytes the bytes of the class being defined
+ * @param classpathEntry the ClasspathEntry where the class bytes have been read from.
+ * @param entry the BundleEntry source of the class bytes
+ * @param manager the class path manager used to define the requested class
+ * @return a modified array of classbytes or null if the original bytes should be used.
+ */
+ byte[] processClass(String name, byte[] classbytes, ClasspathEntry classpathEntry, BundleEntry entry, ClasspathManager manager);
+
+ /**
+ * Gets called by a classpath manager when looking for ClasspathEntry objects. This method allows
+ * a classloading hook to add additional ClasspathEntry objects
+ * @param cpEntries the list of ClasspathEntry objects currently available for the requested classpath
+ * @param cp the name of the requested classpath
+ * @param hostmanager the classpath manager the requested ClasspathEntry is for
+ * @param sourcedata the source bundle data of the requested ClasspathEntry
+ * @param sourcedomain the source domain of the requested ClasspathEntry
+ * @return true if a ClasspathEntry has been added to cpEntries
+ */
+ boolean addClassPathEntry(ArrayList<ClasspathEntry> cpEntries, String cp, ClasspathManager hostmanager, BaseData sourcedata, ProtectionDomain sourcedomain);
+
+ /**
+ * Gets called by a base data during {@link BundleData#findLibrary(String)}.
+ * A base data will call this method for each configured class loading hook until one
+ * class loading hook returns a non-null value. If no class loading hook returns
+ * a non-null value then the base data will return null.
+ * @param data the base data to find a native library for.
+ * @param libName the name of the native library.
+ * @return The absolute path name of the native library or null.
+ */
+ String findLibrary(BaseData data, String libName);
+
+ /**
+ * Gets called by the adaptor during {@link FrameworkAdaptor#getBundleClassLoaderParent()}.
+ * The adaptor will call this method for each configured class loading hook until one
+ * class loading hook returns a non-null value. If no class loading hook returns
+ * a non-null value then the adaptor will perform the default behavior.
+ * @return the parent classloader to be used by all bundle classloaders or null.
+ */
+ public ClassLoader getBundleClassLoaderParent();
+
+ /**
+ * Gets called by a base data during
+ * {@link BundleData#createClassLoader(ClassLoaderDelegate, BundleProtectionDomain, String[])}.
+ * The BaseData will call this method for each configured class loading hook until one data
+ * hook returns a non-null value. If no class loading hook returns a non-null value then a
+ * default implemenation of BundleClassLoader will be created.
+ * @param parent the parent classloader for the BundleClassLoader
+ * @param delegate the delegate for the bundle classloader
+ * @param domain the domian for the bundle classloader
+ * @param data the BundleData for the BundleClassLoader
+ * @param bundleclasspath the classpath for the bundle classloader
+ * @return a newly created bundle classloader
+ */
+ BaseClassLoader createClassLoader(ClassLoader parent, ClassLoaderDelegate delegate, BundleProtectionDomain domain, BaseData data, String[] bundleclasspath);
+
+ /**
+ * Gets called by a classpath manager at the end of
+ * {@link ClasspathManager#initialize()}.
+ * The classpath manager will call this method for each configured class loading hook after it
+ * has been initialized.
+ * @param baseClassLoader the newly created bundle classloader
+ * @param data the BundleData associated with the bundle classloader
+ */
+ void initializedClassLoader(BaseClassLoader baseClassLoader, BaseData data);
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/hooks/ClassLoadingStatsHook.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/hooks/ClassLoadingStatsHook.java
new file mode 100644
index 000000000..726b923bc
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/hooks/ClassLoadingStatsHook.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.baseadaptor.hooks;
+
+import java.net.URL;
+import org.eclipse.osgi.baseadaptor.HookRegistry;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
+import org.eclipse.osgi.baseadaptor.loader.ClasspathEntry;
+import org.eclipse.osgi.baseadaptor.loader.ClasspathManager;
+
+/**
+ * A ClassLoadingStatsHook hooks into the <code>ClasspathManager</code> class. This class allows
+ * a hook to record statistics about classloading.
+ * @see ClasspathManager
+ * @see HookRegistry#getClassLoadingStatsHooks()
+ * @see HookRegistry#addClassLoadingStatsHook(ClassLoadingStatsHook)
+ * @since 3.2
+ */
+public interface ClassLoadingStatsHook {
+ /**
+ * Gets called by a classpath manager during {@link ClasspathManager#findLocalClass(String)} before
+ * searching the local classloader for a class. A classpath manager will call this method for
+ * each configured class loading stat hook.
+ * @param name the name of the requested class
+ * @param manager the classpath manager used to find and load the requested class
+ * @throws ClassNotFoundException to prevent the requested class from loading
+ */
+ void preFindLocalClass(String name, ClasspathManager manager) throws ClassNotFoundException;
+
+ /**
+ * Gets called by a classpath manager during {@link ClasspathManager#findLocalClass(String)} after
+ * searching the local classloader for a class. A classpath manager will call this method for
+ * each configured class loading stat hook.
+ * @param name the name of the requested class
+ * @param clazz the loaded class or null if not found
+ * @param manager the classpath manager used to find and load the requested class
+ */
+ void postFindLocalClass(String name, Class<?> clazz, ClasspathManager manager) throws ClassNotFoundException;
+
+ /**
+ * Gets called by a classpath manager during {@link ClasspathManager#findLocalResource(String)} before
+ * searching the local classloader for a resource. A classpath manager will call this method for
+ * each configured class loading stat hook.
+ * @param name the name of the requested resource
+ * @param manager the classpath manager used to find the requested resource
+ */
+ void preFindLocalResource(String name, ClasspathManager manager);
+
+ /**
+ * Gets called by a classpath manager during {@link ClasspathManager#findLocalResource(String)} after
+ * searching the local classloader for a resource. A classpath manager will call this method for
+ * each configured class loading stat hook.
+ * @param name the name of the requested resource
+ * @param resource the URL to the requested resource or null if not found
+ * @param manager the classpath manager used to find the requested resource
+ */
+ void postFindLocalResource(String name, URL resource, ClasspathManager manager);
+
+ /**
+ * Gets called by a classpath manager after an attempt is made to define a class. This method allows
+ * a class loading stat hook to record data about a class definition.
+ * @param name the name of the class that got defined
+ * @param clazz the class object that got defined or null if an error occurred while defining a class
+ * @param classbytes the class bytes used to define the class
+ * @param classpathEntry the ClasspathEntry where the class bytes got read from
+ * @param entry the BundleEntyr source of the class bytes
+ * @param manager the classpath manager used to define the class
+ */
+ void recordClassDefine(String name, Class<?> clazz, byte[] classbytes, ClasspathEntry classpathEntry, BundleEntry entry, ClasspathManager manager);
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/hooks/StorageHook.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/hooks/StorageHook.java
new file mode 100644
index 000000000..160f2c128
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/hooks/StorageHook.java
@@ -0,0 +1,138 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.baseadaptor.hooks;
+
+import java.io.*;
+import java.util.Dictionary;
+import org.eclipse.osgi.baseadaptor.BaseData;
+import org.eclipse.osgi.framework.adaptor.BundleData;
+import org.eclipse.osgi.framework.util.KeyedElement;
+import org.osgi.framework.BundleException;
+
+/**
+ * A StorageHook hooks into the persistent storage loading and saving. A StorageHook gets
+ * associated with each BaseData object installed in the adaptor.<p>
+ * A StorageHook extends {@link KeyedElement}, the key used for the element must be the
+ * fully qualified string name of the StorageHook implementation class.
+ * @see BaseData#getStorageHook(String)
+ * @since 3.2
+ */
+public interface StorageHook extends KeyedElement {
+ /**
+ * Returns the storage version of this storage hook. This version
+ * is used by the storage to check the consistency of cached persistent
+ * data. Any time a storage hook changes the format of its persistent
+ * data the storage version should be incremented.
+ * @return the storage version of this storage hook
+ */
+ int getStorageVersion();
+
+ /**
+ * Creates an uninitialized storage hook for the specified bundledata. This method
+ * is called when a bundle is installed or updated. The returned storage hook will be
+ * used for the new contents of the bundle. The returned hook will have its
+ * {@link #initialize(Dictionary)} method called to initialize the storage hook.
+ * @param bundledata a base data the created storage hook will be associated with
+ * @return an uninitialized storage hook
+ * @throws BundleException if any error occurs
+ */
+ StorageHook create(BaseData bundledata) throws BundleException;
+
+ /**
+ * Initializes this storage hook with the content of the specified bundle manifest.
+ * This method is called when a bundle is installed or updated.
+ * @see #create(BaseData)
+ * @see #copy(StorageHook)
+ * @param manifest the bundle manifest to load into this storage hook
+ * @throws BundleException if any error occurs
+ */
+ void initialize(Dictionary<String, String> manifest) throws BundleException;
+
+ /**
+ * Creates a new storage hook and loads the data from the specified
+ * input stream into the storage hook. This method is called during startup to
+ * load all the persistently installed bundles. <p>
+ * It is important that this method and the {@link #save(DataOutputStream)} method
+ * stay in sync. This method must be able to successfully read the data saved by the
+ * {@link #save(DataOutputStream)} method.
+ * @param bundledata a base data the loaded storage hook will be associated with
+ * @param is an input stream used to load the storage hook's data from.
+ * @return a loaded storage hook
+ * @see #save(DataOutputStream)
+ * @throws IOException if any error occurs
+ */
+ StorageHook load(BaseData bundledata, DataInputStream is) throws IOException;
+
+ /**
+ * Saves the data from this storage hook into the specified output stream. This method
+ * is called if some persistent data has changed for the bundle. <p>
+ * It is important that this method and the {@link #load(BaseData, DataInputStream)}
+ * method stay in sync. This method must be able to save data which the
+ * {@link #load(BaseData, DataInputStream)} method can ready successfully.
+ * @see #load(BaseData, DataInputStream)
+ * @param os an output stream used to save the storage hook's data from.
+ * @throws IOException if any error occurs
+ */
+ void save(DataOutputStream os) throws IOException;
+
+ /**
+ * Copies the data from the specified storage hook into this storage hook. This method
+ * is called when a bundle is updated to copy the data from the original bundle to a
+ * new storage hook. Then this storage will be initialized with the new bundle's
+ * manifest using the {@link #initialize(Dictionary)} method.
+ * @see #create(BaseData)
+ * @see #initialize(Dictionary)
+ * @param storageHook the original storage hook to copy data out of.
+ */
+ void copy(StorageHook storageHook);
+
+ /**
+ * Validates the data in this storage hook, if the data is invalid then an illegal state
+ * exception is thrown
+ * @throws IllegalArgumentException if the data is invalid
+ */
+ void validate() throws IllegalArgumentException;
+
+ /**
+ * Returns the manifest for the data in this storage hook, or null if this hook does
+ * not provide the manifest. Most hooks should return null from this method. This
+ * method may be used to provide special handling of manifest loading. For example,
+ * to provide a cached manfest or to do automatic manifest generation.
+ * @param firstLoad true if this is the very first time this manifest is being loaded.
+ * @return the manifest for the data in this storage hook, or null if this hook does
+ * not provide the manifest
+ * @throws BundleException
+ */
+ Dictionary<String, String> getManifest(boolean firstLoad) throws BundleException;
+
+ /**
+ * Gets called by a base data during {@link BundleData#setStatus(int)}.
+ * A base data will call this method for each configured storage hook it
+ * is associated with until one storage hook returns true. If all configured storage
+ * hooks return false then the BaseData will be marked dirty and will cause the
+ * status to be persistently saved.
+ * @param status the new status of the base data
+ * @return false if the status is not to be persistently saved; otherwise true is returned
+ */
+ boolean forgetStatusChange(int status);
+
+ /**
+ * Gets called by a base data during {@link BundleData#setStartLevel(int)}.
+ * A base data will call this method for each configured storage hook it
+ * is associated with until one storage hook returns true. If all configured storage
+ * hooks return false then the BaseData will be marked dirty and will cause the
+ * start level to be persistently saved.
+ * @param startlevel the new startlevel of the base data
+ * @return false if the startlevel is not to be persistently saved; otherwise true is returned
+ */
+ boolean forgetStartLevelChange(int startlevel);
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/loader/BaseClassLoader.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/loader/BaseClassLoader.java
new file mode 100644
index 000000000..5f727076a
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/loader/BaseClassLoader.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.baseadaptor.loader;
+
+import java.net.URL;
+import java.security.ProtectionDomain;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile;
+import org.eclipse.osgi.framework.adaptor.BundleClassLoader;
+
+/**
+ * The actual class loader object used to define classes for a classpath manager.
+ * This interface provides public versions of a few methods on class loader.
+ * @see ClasspathManager
+ * @since 3.2
+ */
+public interface BaseClassLoader extends BundleClassLoader {
+ /**
+ * Returns the domain for the host bundle of this class loader
+ * @return the domain for the host bundle of this class loader
+ */
+ ProtectionDomain getDomain();
+
+ /**
+ * Creates a classpath entry with the given bundle file and domain
+ * @param bundlefile the source bundle file for a classpath entry
+ * @param cpDomain the source domain for a classpath entry
+ * @return a classpath entry with the given bundle file and domain
+ */
+ ClasspathEntry createClassPathEntry(BundleFile bundlefile, ProtectionDomain cpDomain);
+
+ /**
+ * Defines a Class.
+ * @param name the name of the class to define
+ * @param classbytes the bytes of the class to define
+ * @param classpathEntry the classpath entry used to load the class bytes
+ * @param entry the bundle entry used to load the class bytes
+ * @return a defined Class
+ */
+ Class<?> defineClass(String name, byte[] classbytes, ClasspathEntry classpathEntry, BundleEntry entry);
+
+ /**
+ * A public version of the ClassLoader.findLoadedClass(java.lang.String) method.
+ * @param classname the class name to find.
+ * @return a loaded class
+ */
+ Class<?> publicFindLoaded(String classname);
+
+ /**
+ * A public version of the ClassLoader#getPackage(java.lang.String) method.
+ * @param pkgname the package name to get.
+ * @return the package or null if it does not exist
+ */
+ Object publicGetPackage(String pkgname);
+
+ /**
+ * A public version of the ClassLoader#definePackage(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.net.URL) method.
+ * @return a defined Package
+ */
+ Object publicDefinePackage(String name, String specTitle, String specVersion, String specVendor, String implTitle, String implVersion, String implVendor, URL sealBase);
+
+ /**
+ * Returns the ClasspathManager for this BaseClassLoader
+ * @return the ClasspathManager
+ */
+ ClasspathManager getClasspathManager();
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/loader/ClasspathEntry.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/loader/ClasspathEntry.java
new file mode 100644
index 000000000..27fc5c4a6
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/loader/ClasspathEntry.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2009 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.baseadaptor.loader;
+
+import java.security.ProtectionDomain;
+import org.eclipse.osgi.baseadaptor.BaseData;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile;
+import org.eclipse.osgi.framework.util.KeyedElement;
+import org.eclipse.osgi.framework.util.KeyedHashSet;
+
+/**
+ * A ClasspathEntry contains a single <code>BundleFile</code> which is used as
+ * a source to load classes and resources from, and a single
+ * <code>ProtectionDomain</code> which is used as the domain to define classes
+ * loaded from this ClasspathEntry.
+ * @since 3.2
+ */
+public class ClasspathEntry {
+ private final BundleFile bundlefile;
+ private final ProtectionDomain domain;
+ private KeyedHashSet userObjects = null;
+ // Note that PDE has internal dependency on this field type/name (bug 267238)
+ private volatile BaseData data;
+
+ /**
+ * Constructs a ClasspathElement with the specified bundlefile and domain
+ * @param bundlefile A BundleFile object which acts as a source
+ * @param domain the ProtectDomain for all code loaded from this classpath element
+ */
+ public ClasspathEntry(BundleFile bundlefile, ProtectionDomain domain) {
+ this.bundlefile = bundlefile;
+ this.domain = domain;
+ }
+
+ /**
+ * Returns the source BundleFile for this classpath entry
+ * @return the source BundleFile for this classpath entry
+ */
+ public BundleFile getBundleFile() {
+ return bundlefile;
+ }
+
+ /**
+ * Returns the base data which this entry is associated with. This can be
+ * either a host or fragment base data.
+ */
+ public BaseData getBaseData() {
+ return data;
+ }
+
+ void setBaseData(BaseData data) {
+ this.data = data;
+ }
+
+ /**
+ * Returns the ProtectionDomain for this classpath entry
+ * @return the ProtectionDomain for this classpath entry
+ */
+ public ProtectionDomain getDomain() {
+ return domain;
+ }
+
+ /**
+ * Returns a user object which is keyed by the specified key
+ * @param key the key of the user object to get
+ * @return a user object which is keyed by the specified key
+ */
+ public Object getUserObject(Object key) {
+ if (userObjects == null)
+ return null;
+ synchronized (userObjects) {
+ return userObjects.getByKey(key);
+ }
+ }
+
+ /**
+ * Adds a user object
+ * @param userObject the user object to add
+ */
+ public synchronized void addUserObject(KeyedElement userObject) {
+ if (userObjects == null)
+ userObjects = new KeyedHashSet(5, false);
+ synchronized (userObjects) {
+ userObjects.add(userObject);
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/loader/ClasspathManager.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/loader/ClasspathManager.java
new file mode 100644
index 000000000..bba55ad7b
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/loader/ClasspathManager.java
@@ -0,0 +1,737 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.baseadaptor.loader;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.security.ProtectionDomain;
+import java.util.*;
+import org.eclipse.osgi.baseadaptor.BaseAdaptor;
+import org.eclipse.osgi.baseadaptor.BaseData;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile;
+import org.eclipse.osgi.baseadaptor.hooks.ClassLoadingHook;
+import org.eclipse.osgi.baseadaptor.hooks.ClassLoadingStatsHook;
+import org.eclipse.osgi.framework.adaptor.*;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.framework.internal.core.FrameworkProperties;
+import org.eclipse.osgi.internal.baseadaptor.AdaptorMsg;
+import org.eclipse.osgi.internal.baseadaptor.ArrayMap;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.FrameworkEvent;
+
+/**
+ * A helper class for <code>BaseClassLoader</code> implementations. This class will keep track of
+ * <code>ClasspathEntry</code> objects for the host bundle and any attached fragment bundles. This
+ * class takes care of searching the <code>ClasspathEntry</code> objects for a base class loader
+ * implementation. Additional behavior may be added to a classpath manager by configuring
+ * <code>ClassLoadingHook</code> and <code>ClassLoadingStatsHook</code>.
+ * @see BaseClassLoader
+ * @see ClassLoadingHook
+ * @see ClassLoadingStatsHook
+ * @since 3.2
+ */
+public class ClasspathManager {
+ private static final FragmentClasspath[] emptyFragments = new FragmentClasspath[0];
+ private final static String PROP_CLASSLOADER_LOCK = "osgi.classloader.lock"; //$NON-NLS-1$
+ private final static String VALUE_CLASSNAME_LOCK = "classname"; //$NON-NLS-1$
+ private final static boolean LOCK_CLASSNAME = VALUE_CLASSNAME_LOCK.equals(FrameworkProperties.getProperty(PROP_CLASSLOADER_LOCK));
+ private final static Class<?>[] NULL_CLASS_RESULT = new Class[2];
+
+ private final BaseData data;
+ private final String[] classpath;
+ private final BaseClassLoader classloader;
+ private final boolean isParallelClassLoader;
+ private final Map<String, Thread> classNameLocks = new HashMap<String, Thread>(5);
+
+ // Note that PDE has internal dependency on this field type/name (bug 267238)
+ private ClasspathEntry[] entries;
+ // Note that PDE has internal dependency on this field type/name (bug 267238)
+ private FragmentClasspath[] fragments = emptyFragments;
+ // a Map<String,String> where "libname" is the key and libpath" is the value
+ private ArrayMap<String, String> loadedLibraries = null;
+ // used to detect recusive defineClass calls for the same class on the same class loader (bug 345500)
+ private ThreadLocal<Collection<String>> currentlyDefining = new ThreadLocal<Collection<String>>();
+
+ /**
+ * Constructs a classpath manager for the given host base data, classpath and base class loader
+ * @param data the host base data for this classpath manager
+ * @param classpath the host classpath for this classpath manager
+ * @param classloader the BaseClassLoader for this classpath manager
+ */
+ public ClasspathManager(BaseData data, String[] classpath, BaseClassLoader classloader) {
+ this.data = data;
+ this.classpath = classpath;
+ this.classloader = classloader;
+ isParallelClassLoader = (classloader instanceof ParallelClassLoader) ? ((ParallelClassLoader) classloader).isParallelCapable() : false;
+ }
+
+ /**
+ * initializes this classpath manager. This must be called after all existing fragments have been
+ * attached and before any resources/classes are loaded using this classpath manager.
+ * <p>
+ * After the classpath manager is initialized all configured class loading hooks
+ * {@link ClassLoadingHook#initializedClassLoader(BaseClassLoader, BaseData)} methods are called.
+ * </p>
+ */
+ public void initialize() {
+ entries = buildClasspath(classpath, this, data, classloader == null ? null : classloader.getDomain());
+ ClassLoadingHook[] hooks = data.getAdaptor().getHookRegistry().getClassLoadingHooks();
+ if (classloader != null)
+ for (int i = 0; i < hooks.length; i++)
+ hooks[i].initializedClassLoader(classloader, data);
+ }
+
+ /**
+ * Closes all the classpath entry resources for this classpath manager.
+ *
+ */
+ public void close() {
+ if (entries != null) {
+ for (int i = 0; i < entries.length; i++) {
+ if (entries[i] != null) {
+ try {
+ entries[i].getBundleFile().close();
+ } catch (IOException e) {
+ data.getAdaptor().getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, data.getBundle(), e);
+ }
+ }
+ }
+ }
+ for (int i = 0; i < fragments.length; i++)
+ fragments[i].close();
+ }
+
+ /**
+ * Attaches the specified sourcedata, sourcedomain and sourceclasspath to this classpath manager
+ * @param sourcedata the source fragment BundleData that should be attached.
+ * @param sourcedomain the source fragment domain that should be attached.
+ * @param sourceclasspath the source fragment classpath that should be attached.
+ */
+ public void attachFragment(BundleData sourcedata, ProtectionDomain sourcedomain, String[] sourceclasspath) {
+ try {
+ sourcedata.open(); /* make sure the BundleData is open */
+ } catch (IOException e) {
+ ((BaseData) sourcedata).getAdaptor().getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, ((BaseData) sourcedata).getBundle(), e);
+ }
+ ClasspathEntry[] fragEntries = buildClasspath(sourceclasspath, this, (BaseData) sourcedata, sourcedomain);
+ FragmentClasspath fragClasspath = new FragmentClasspath((BaseData) sourcedata, fragEntries, sourcedomain);
+ insertFragment(fragClasspath);
+ }
+
+ private synchronized void insertFragment(FragmentClasspath fragClasspath) {
+ FragmentClasspath[] newFragments = new FragmentClasspath[fragments.length + 1];
+ // Find a place in the fragment list to insert this fragment.
+ long fragID = fragClasspath.getBundleData().getBundleID();
+ int insert = 0;
+ for (int i = 0; i < fragments.length; i++) {
+ long otherID = fragments[i].getBundleData().getBundleID();
+ if (insert == 0 && fragID < otherID) {
+ newFragments[i] = fragClasspath;
+ insert = 1;
+ }
+ newFragments[i + insert] = fragments[i];
+ }
+ // This fragment has the highest ID; put it at the end of the list.
+ if (insert == 0)
+ newFragments[fragments.length] = fragClasspath;
+ fragments = newFragments;
+ }
+
+ private static ClasspathEntry[] buildClasspath(String[] cp, ClasspathManager hostloader, BaseData sourcedata, ProtectionDomain sourcedomain) {
+ ArrayList<ClasspathEntry> result = new ArrayList<ClasspathEntry>(cp.length);
+ // add the regular classpath entries.
+ for (int i = 0; i < cp.length; i++)
+ findClassPathEntry(result, cp[i], hostloader, sourcedata, sourcedomain);
+ return result.toArray(new ClasspathEntry[result.size()]);
+ }
+
+ /**
+ * Finds all the ClasspathEntry objects for the requested classpath. This method will first call all
+ * the configured class loading hooks {@link ClassLoadingHook#addClassPathEntry(ArrayList, String, ClasspathManager, BaseData, ProtectionDomain)}
+ * methods. This allows class loading hooks to add additional ClasspathEntry objects to the result for the
+ * requested classpath. Then the local host classpath entries and attached fragment classpath entries are
+ * searched.
+ * @param result a list of ClasspathEntry objects. This list is used to add new ClasspathEntry objects to.
+ * @param cp the requested classpath.
+ * @param hostloader the host classpath manager for the classpath
+ * @param sourcedata the source EquionoxData to search for the classpath
+ * @param sourcedomain the source domain to used by the new ClasspathEntry
+ */
+ public static void findClassPathEntry(ArrayList<ClasspathEntry> result, String cp, ClasspathManager hostloader, BaseData sourcedata, ProtectionDomain sourcedomain) {
+ // look in classpath manager hooks first
+ ClassLoadingHook[] loaderHooks = sourcedata.getAdaptor().getHookRegistry().getClassLoadingHooks();
+ boolean hookAdded = false;
+ for (int i = 0; i < loaderHooks.length; i++)
+ hookAdded |= loaderHooks[i].addClassPathEntry(result, cp, hostloader, sourcedata, sourcedomain);
+ if (!addClassPathEntry(result, cp, hostloader, sourcedata, sourcedomain) && !hookAdded) {
+ BundleException be = new BundleException(NLS.bind(AdaptorMsg.BUNDLE_CLASSPATH_ENTRY_NOT_FOUND_EXCEPTION, cp, sourcedata.getLocation()), BundleException.MANIFEST_ERROR);
+ sourcedata.getAdaptor().getEventPublisher().publishFrameworkEvent(FrameworkEvent.INFO, sourcedata.getBundle(), be);
+ }
+ }
+
+ /**
+ * Adds a ClasspathEntry for the requested classpath to the result. The local host classpath entries
+ * are searched first and then attached fragments classpath entries are searched. The search stops once the first
+ * classpath entry is found.
+ * @param result a list of ClasspathEntry objects. This list is used to add new ClasspathEntry objects to.
+ * @param cp the requested classpath.
+ * @param hostloader the host classpath manager for the classpath
+ * @param sourcedata the source EquionoxData to search for the classpath
+ * @param sourcedomain the source domain to used by the new ClasspathEntry
+ * @return true if a ClasspathEntry was added to the result
+ */
+ public static boolean addClassPathEntry(ArrayList<ClasspathEntry> result, String cp, ClasspathManager hostloader, BaseData sourcedata, ProtectionDomain sourcedomain) {
+ if (cp.equals(".")) { //$NON-NLS-1$
+ result.add(hostloader.createClassPathEntry(sourcedata.getBundleFile(), sourcedomain, sourcedata));
+ return true;
+ }
+ ClasspathEntry element = hostloader.getClasspath(cp, sourcedata, sourcedomain);
+ if (element != null) {
+ result.add(element);
+ return true;
+ }
+ // need to check in fragments for the classpath entry.
+ // only check for fragments if the data is the host's data.
+ if (hostloader.data == sourcedata)
+ for (int i = 0; i < hostloader.fragments.length; i++) {
+ FragmentClasspath fragCP = hostloader.fragments[i];
+ element = hostloader.getClasspath(cp, fragCP.getBundleData(), fragCP.getDomain());
+ if (element != null) {
+ result.add(element);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Creates a new ClasspathEntry object for the requested classpath if the source exists.
+ * @param cp the requested classpath.
+ * @param sourcedata the source EquionoxData to search for the classpath
+ * @param sourcedomain the source domain to used by the new ClasspathEntry
+ * @return a new ClasspathEntry for the requested classpath or null if the source does not exist.
+ */
+ public ClasspathEntry getClasspath(String cp, BaseData sourcedata, ProtectionDomain sourcedomain) {
+ BundleFile bundlefile = null;
+ File file;
+ BundleEntry cpEntry = sourcedata.getBundleFile().getEntry(cp);
+ // check for internal library directories in a bundle jar file
+ if (cpEntry != null && cpEntry.getName().endsWith("/")) //$NON-NLS-1$
+ bundlefile = createBundleFile(cp, sourcedata);
+ // check for internal library jars
+ else if ((file = sourcedata.getBundleFile().getFile(cp, false)) != null)
+ bundlefile = createBundleFile(file, sourcedata);
+ if (bundlefile != null)
+ return createClassPathEntry(bundlefile, sourcedomain, sourcedata);
+ return null;
+ }
+
+ /**
+ * Uses the requested classpath as an absolute path to locate a source for a new ClasspathEntry.
+ * @param cp the requested classpath
+ * @param sourcedata the source EquionoxData to search for the classpath
+ * @param sourcedomain the source domain to used by the new ClasspathEntry
+ * @return a classpath entry which uses an absolut path as a source
+ */
+ public ClasspathEntry getExternalClassPath(String cp, BaseData sourcedata, ProtectionDomain sourcedomain) {
+ File file = new File(cp);
+ if (!file.isAbsolute())
+ return null;
+ BundleFile bundlefile = createBundleFile(file, sourcedata);
+ if (bundlefile != null)
+ return createClassPathEntry(bundlefile, sourcedomain, sourcedata);
+ return null;
+ }
+
+ private static BundleFile createBundleFile(Object content, BaseData sourcedata) {
+ if (content == null || (content instanceof File && !((File) content).exists()))
+ return null;
+ try {
+ return sourcedata.getAdaptor().createBundleFile(content, sourcedata);
+ } catch (IOException e) {
+ sourcedata.getAdaptor().getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, sourcedata.getBundle(), e);
+ }
+ return null;
+ }
+
+ private ClasspathEntry createClassPathEntry(BundleFile bundlefile, ProtectionDomain cpDomain, BaseData cpData) {
+ ClasspathEntry entry;
+ if (classloader != null)
+ entry = classloader.createClassPathEntry(bundlefile, cpDomain);
+ else
+ entry = new ClasspathEntry(bundlefile, null);
+ entry.setBaseData(cpData);
+ Object domain = entry.getDomain();
+ if (domain instanceof BundleProtectionDomain)
+ ((BundleProtectionDomain) domain).setBundle(cpData.getBundle());
+ return entry;
+ }
+
+ /**
+ * Finds a local resource by searching the ClasspathEntry objects of the classpath manager.
+ * This method will first call all the configured class loading stats hooks
+ * {@link ClassLoadingStatsHook#preFindLocalResource(String, ClasspathManager)} methods. Then it
+ * will search for the resource. Finally it will call all the configured class loading stats hooks
+ * {@link ClassLoadingStatsHook#postFindLocalResource(String, URL, ClasspathManager)} methods.
+ * @param resource the requested resource name.
+ * @return the requested resource URL or null if the resource does not exist
+ */
+ public URL findLocalResource(String resource) {
+ ClassLoadingStatsHook[] hooks = data.getAdaptor().getHookRegistry().getClassLoadingStatsHooks();
+ for (int i = 0; i < hooks.length; i++)
+ hooks[i].preFindLocalResource(resource, this);
+ URL result = null;
+ try {
+ result = findLocalResourceImpl(resource, -1);
+ return result;
+ } finally {
+ for (int i = 0; i < hooks.length; i++)
+ hooks[i].postFindLocalResource(resource, result, this);
+ }
+ }
+
+ private URL findLocalResourceImpl(String resource, int classPathIndex) {
+ URL result = null;
+ int curIndex = 0;
+ for (int i = 0; i < entries.length; i++) {
+ if (entries[i] != null) {
+ result = findResourceImpl(resource, entries[i].getBundleFile(), curIndex);
+ if (result != null && (classPathIndex == -1 || classPathIndex == curIndex))
+ return result;
+ }
+ curIndex++;
+ }
+ // look in fragments
+ for (int i = 0; i < fragments.length; i++) {
+ ClasspathEntry[] fragEntries = fragments[i].getEntries();
+ for (int j = 0; j < fragEntries.length; j++) {
+ result = findResourceImpl(resource, fragEntries[j].getBundleFile(), curIndex);
+ if (result != null && (classPathIndex == -1 || classPathIndex == curIndex))
+ return result;
+ curIndex++;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Finds the local resources by searching the ClasspathEntry objects of the classpath manager.
+ * @param resource the requested resource name.
+ * @return an enumeration of the the requested resources or null if the resources do not exist
+ */
+ public Enumeration<URL> findLocalResources(String resource) {
+ List<URL> resources = new ArrayList<URL>(6);
+ int classPathIndex = 0;
+ for (int i = 0; i < entries.length; i++) {
+ if (entries[i] != null) {
+ URL url = findResourceImpl(resource, entries[i].getBundleFile(), classPathIndex);
+ if (url != null)
+ resources.add(url);
+ }
+ classPathIndex++;
+ }
+ // look in fragments
+ for (int i = 0; i < fragments.length; i++) {
+ ClasspathEntry[] fragEntries = fragments[i].getEntries();
+ for (int j = 0; j < fragEntries.length; j++) {
+ URL url = findResourceImpl(resource, fragEntries[j].getBundleFile(), classPathIndex);
+ if (url != null)
+ resources.add(url);
+ classPathIndex++;
+ }
+ }
+ if (resources.size() > 0)
+ return Collections.enumeration(resources);
+ return null;
+ }
+
+ private URL findResourceImpl(String name, BundleFile bundlefile, int index) {
+ return bundlefile.getResourceURL(name, data, index);
+ }
+
+ /**
+ * Finds a local entry by searching the ClasspathEntry objects of the classpath manager.
+ * @param path the requested entry path.
+ * @return the requested entry or null if the entry does not exist
+ */
+ public BundleEntry findLocalEntry(String path) {
+ return findLocalEntry(path, -1);
+ }
+
+ /**
+ * Finds a local entry by searching the ClasspathEntry with the specified
+ * class path index.
+ * @param path the requested entry path.
+ * @param classPathIndex the index of the ClasspathEntry to search
+ * @return the requested entry or null if the entry does not exist
+ */
+ public BundleEntry findLocalEntry(String path, int classPathIndex) {
+ BundleEntry result = null;
+ int curIndex = 0;
+ for (int i = 0; i < entries.length; i++) {
+ if (entries[i] != null) {
+ result = findEntryImpl(path, entries[i].getBundleFile());
+ if (result != null && (classPathIndex == -1 || classPathIndex == curIndex))
+ return result;
+ }
+ curIndex++;
+ }
+ // look in fragments
+ for (int i = 0; i < fragments.length; i++) {
+ ClasspathEntry[] fragEntries = fragments[i].getEntries();
+ for (int j = 0; j < fragEntries.length; j++) {
+ result = findEntryImpl(path, fragEntries[j].getBundleFile());
+ if (result != null && (classPathIndex == -1 || classPathIndex == curIndex))
+ return result;
+ curIndex++;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Finds the local entries by searching the ClasspathEntry objects of the classpath manager.
+ * @param path the requested entry path.
+ * @return an enumeration of the the requested entries or null if the entries do not exist
+ */
+ public Enumeration<BundleEntry> findLocalEntries(String path) {
+ List<BundleEntry> objects = new ArrayList<BundleEntry>(6);
+ for (int i = 0; i < entries.length; i++) {
+ if (entries[i] != null) {
+ BundleEntry result = findEntryImpl(path, entries[i].getBundleFile());
+ if (result != null)
+ objects.add(result);
+ }
+ }
+ // look in fragments
+ for (int i = 0; i < fragments.length; i++) {
+ ClasspathEntry[] fragEntries = fragments[i].getEntries();
+ for (int j = 0; j < fragEntries.length; j++) {
+ BundleEntry result = findEntryImpl(path, fragEntries[j].getBundleFile());
+ if (result != null)
+ objects.add(result);
+ }
+ }
+ if (objects.size() > 0)
+ return Collections.enumeration(objects);
+ return null;
+ }
+
+ private BundleEntry findEntryImpl(String path, BundleFile bundleFile) {
+ return bundleFile.getEntry(path);
+ }
+
+ /**
+ * Finds a local class by searching the ClasspathEntry objects of the classpath manager.
+ * This method will first call all the configured class loading stats hooks
+ * {@link ClassLoadingStatsHook#preFindLocalClass(String, ClasspathManager)} methods. Then it
+ * will search for the class. If a class is found then
+ * <ol>
+ * <li>All configured class loading hooks
+ * {@link ClassLoadingHook#processClass(String, byte[], ClasspathEntry, BundleEntry, ClasspathManager)}
+ * methods will be called.</li>
+ * <li>The class is then defined.</li>
+ * <li>Finally, all configured class loading
+ * stats hooks {@link ClassLoadingStatsHook#recordClassDefine(String, Class, byte[], ClasspathEntry, BundleEntry, ClasspathManager)}
+ * methods are called.</li>
+ * </ol>
+ * Finally all the configured class loading stats hooks
+ * {@link ClassLoadingStatsHook#postFindLocalClass(String, Class, ClasspathManager)} methods are called.
+ * @param classname the requested class name.
+ * @return the requested class
+ * @throws ClassNotFoundException if the class does not exist
+ */
+ public Class<?> findLocalClass(String classname) throws ClassNotFoundException {
+ Class<?> result = null;
+ ClassLoadingStatsHook[] hooks = data.getAdaptor().getHookRegistry().getClassLoadingStatsHooks();
+ try {
+ for (int i = 0; i < hooks.length; i++)
+ hooks[i].preFindLocalClass(classname, this);
+ result = findLoadedClass(classname);
+ if (result != null)
+ return result;
+ result = findLocalClassImpl(classname, hooks);
+ return result;
+ } finally {
+ for (int i = 0; i < hooks.length; i++)
+ hooks[i].postFindLocalClass(classname, result, this);
+ }
+ }
+
+ private Class<?> findLoadedClass(String classname) {
+ if (LOCK_CLASSNAME || isParallelClassLoader) {
+ boolean initialLock = lockClassName(classname);
+ try {
+ return classloader.publicFindLoaded(classname);
+ } finally {
+ if (initialLock)
+ unlockClassName(classname);
+ }
+ }
+ synchronized (classloader) {
+ return classloader.publicFindLoaded(classname);
+ }
+ }
+
+ private Class<?> findLocalClassImpl(String classname, ClassLoadingStatsHook[] hooks) throws ClassNotFoundException {
+ Class<?> result = null;
+ for (int i = 0; i < entries.length; i++) {
+ if (entries[i] != null) {
+ result = findClassImpl(classname, entries[i], hooks);
+ if (result != null)
+ return result;
+ }
+ }
+ // look in fragments.
+ for (int i = 0; i < fragments.length; i++) {
+ ClasspathEntry[] fragEntries = fragments[i].getEntries();
+ for (int j = 0; j < fragEntries.length; j++) {
+ result = findClassImpl(classname, fragEntries[j], hooks);
+ if (result != null)
+ return result;
+ }
+ }
+ throw new ClassNotFoundException(classname);
+ }
+
+ private boolean lockClassName(String classname) {
+ synchronized (classNameLocks) {
+ Object lockingThread = classNameLocks.get(classname);
+ Thread current = Thread.currentThread();
+ if (lockingThread == current)
+ return false;
+ while (true) {
+ if (lockingThread == null) {
+ classNameLocks.put(classname, current);
+ return true;
+ }
+ try {
+ classNameLocks.wait();
+ lockingThread = classNameLocks.get(classname);
+ } catch (InterruptedException e) {
+ current.interrupt();
+ throw (LinkageError) new LinkageError(classname).initCause(e);
+ }
+ }
+ }
+ }
+
+ private void unlockClassName(String classname) {
+ synchronized (classNameLocks) {
+ classNameLocks.remove(classname);
+ classNameLocks.notifyAll();
+ }
+ }
+
+ private Class<?> findClassImpl(String name, ClasspathEntry classpathEntry, ClassLoadingStatsHook[] hooks) {
+ if (Debug.DEBUG_LOADER)
+ Debug.println("BundleClassLoader[" + classpathEntry.getBundleFile() + "].findClassImpl(" + name + ")"); //$NON-NLS-1$ //$NON-NLS-2$//$NON-NLS-3$
+ String filename = name.replace('.', '/').concat(".class"); //$NON-NLS-1$
+ BundleEntry entry = classpathEntry.getBundleFile().getEntry(filename);
+ if (entry == null)
+ return null;
+
+ byte[] classbytes;
+ try {
+ classbytes = entry.getBytes();
+ } catch (IOException e) {
+ if (Debug.DEBUG_LOADER)
+ Debug.println(" IOException reading " + filename + " from " + classpathEntry.getBundleFile()); //$NON-NLS-1$ //$NON-NLS-2$
+ throw (LinkageError) new LinkageError("Error reading class bytes: " + name).initCause(e); //$NON-NLS-1$
+ }
+ if (Debug.DEBUG_LOADER) {
+ Debug.println(" read " + classbytes.length + " bytes from " + classpathEntry.getBundleFile() + "/" + filename); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ Debug.println(" defining class " + name); //$NON-NLS-1$
+ }
+
+ Collection<String> current = currentlyDefining.get();
+ if (current == null) {
+ current = new ArrayList<String>(5);
+ currentlyDefining.set(current);
+ }
+ if (current.contains(name))
+ return null; // avoid recursive defines (bug 345500)
+ try {
+ current.add(name);
+ return defineClass(name, classbytes, classpathEntry, entry, hooks);
+ } catch (Error e) {
+ if (Debug.DEBUG_LOADER)
+ Debug.println(" error defining class " + name); //$NON-NLS-1$
+ throw e;
+ } finally {
+ current.remove(name);
+ }
+ }
+
+ /**
+ * Defines the specified class. This method will first call all the configured class loading hooks
+ * {@link ClassLoadingHook#processClass(String, byte[], ClasspathEntry, BundleEntry, ClasspathManager)}
+ * methods. Then it will call the {@link BaseClassLoader#defineClass(String, byte[], ClasspathEntry, BundleEntry)}
+ * method to define the class. After that, the class loading stat hooks are called to announce the class
+ * definition.
+ * @param name the name of the class to define
+ * @param classbytes the class bytes
+ * @param classpathEntry the classpath entry used to load the class bytes
+ * @param entry the BundleEntry used to load the class bytes
+ * @param statsHooks the class loading stat hooks
+ * @return the defined class
+ */
+ private Class<?> defineClass(String name, byte[] classbytes, ClasspathEntry classpathEntry, BundleEntry entry, ClassLoadingStatsHook[] statsHooks) {
+ ClassLoadingHook[] hooks = data.getAdaptor().getHookRegistry().getClassLoadingHooks();
+ byte[] modifiedBytes = classbytes;
+ // The result holds two Class objects.
+ // The first slot to either a pre loaded class or the newly defined class.
+ // The second slot is only set to a newly defined class object if it was successfully defined
+ Class<?>[] result = NULL_CLASS_RESULT;
+ try {
+ for (int i = 0; i < hooks.length; i++) {
+ modifiedBytes = hooks[i].processClass(name, classbytes, classpathEntry, entry, this);
+ if (modifiedBytes != null)
+ classbytes = modifiedBytes;
+ }
+ if (LOCK_CLASSNAME || isParallelClassLoader) {
+ boolean initialLock = lockClassName(name);
+ try {
+ result = defineClassHoldingLock(name, classbytes, classpathEntry, entry);
+ } finally {
+ if (initialLock)
+ unlockClassName(name);
+ }
+ } else {
+ synchronized (classloader) {
+ result = defineClassHoldingLock(name, classbytes, classpathEntry, entry);
+ }
+ }
+ } finally {
+ for (int i = 0; i < statsHooks.length; i++)
+ // only pass the newly defined class to the hook
+ statsHooks[i].recordClassDefine(name, result[1], classbytes, classpathEntry, entry, this);
+ }
+ // return either the pre-loaded class or the newly defined class
+ return result[0];
+ }
+
+ private Class<?>[] defineClassHoldingLock(String name, byte[] classbytes, ClasspathEntry classpathEntry, BundleEntry entry) {
+ Class<?>[] result = new Class[2];
+ // must call findLoadedClass here even if it was called earlier,
+ // the findLoadedClass and defineClass calls must be atomic
+ result[0] = classloader.publicFindLoaded(name);
+ if (result[0] == null)
+ result[0] = result[1] = classloader.defineClass(name, classbytes, classpathEntry, entry);
+ return result;
+ }
+
+ /**
+ * Returns the host base data for this classpath manager
+ * @return the host base data for this classpath manager
+ */
+ public BaseData getBaseData() {
+ return data;
+ }
+
+ /**
+ * Returns the fragment classpaths of this classpath manager
+ * @return the fragment classpaths of this classpath manager
+ */
+ public FragmentClasspath[] getFragmentClasspaths() {
+ return fragments;
+ }
+
+ /**
+ * Returns the host classpath entries for this classpath manager
+ * @return the host classpath entries for this classpath manager
+ */
+ public ClasspathEntry[] getHostClasspathEntries() {
+ return entries;
+ }
+
+ /**
+ * Returns the base class loader used by this classpath manager
+ * @return the base class loader used by this classpath manager
+ */
+ public BaseClassLoader getBaseClassLoader() {
+ return classloader;
+ }
+
+ /**
+ * Finds a library for the bundle represented by this class path managert
+ * @param libname the library name
+ * @return The absolution path to the library or null if not found
+ */
+ public String findLibrary(String libname) {
+ synchronized (this) {
+ if (loadedLibraries == null)
+ loadedLibraries = new ArrayMap<String, String>(1);
+ }
+ synchronized (loadedLibraries) {
+ // we assume that each classloader will load a small number of of libraries
+ // instead of wasting space with a map we iterate over our collection of found libraries
+ // each element is a String[2], each array is {"libname", "libpath"}
+ String libpath = loadedLibraries.get(libname);
+ if (libpath != null)
+ return libpath;
+
+ libpath = classloader.getDelegate().findLibrary(libname);
+ if (libpath != null)
+ loadedLibraries.put(libname, libpath);
+ return libpath;
+ }
+ }
+
+ /**
+ * @see BundleClassLoader#findEntries(String, String, int)
+ */
+ public List<URL> findEntries(String path, String filePattern, int options) {
+ BaseAdaptor adaptor = getBaseData().getAdaptor();
+ List<BundleData> datas = new ArrayList<BundleData>();
+ // first get the host bundle file
+ datas.add(getBaseData());
+ // next get the attached fragments bundle files
+ FragmentClasspath[] currentFragments = getFragmentClasspaths();
+ for (FragmentClasspath fragmentClasspath : currentFragments)
+ datas.add(fragmentClasspath.getBundleData());
+
+ @SuppressWarnings("unchecked")
+ List<URL> result = Collections.EMPTY_LIST;
+ // now search over all the bundle files
+ Enumeration<URL> eURLs = adaptor.findEntries(datas, path, filePattern, options);
+ if (eURLs == null)
+ return result;
+ result = new ArrayList<URL>();
+ while (eURLs.hasMoreElements())
+ result.add(eURLs.nextElement());
+ return Collections.unmodifiableList(result);
+ }
+
+ /**
+ * @see BundleClassLoader#listLocalResources(String, String, int)
+ */
+ public Collection<String> listLocalResources(String path, String filePattern, int options) {
+ List<BundleFile> bundleFiles = new ArrayList<BundleFile>();
+
+ ClasspathEntry[] cpEntries = getHostClasspathEntries();
+ for (ClasspathEntry cpEntry : cpEntries)
+ bundleFiles.add(cpEntry.getBundleFile());
+
+ FragmentClasspath[] currentFragments = getFragmentClasspaths();
+ for (FragmentClasspath fragmentClasspath : currentFragments) {
+ ClasspathEntry[] fragEntries = fragmentClasspath.getEntries();
+ for (ClasspathEntry cpEntry : fragEntries)
+ bundleFiles.add(cpEntry.getBundleFile());
+ }
+
+ return getBaseData().getAdaptor().listEntryPaths(bundleFiles, path, filePattern, options);
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/loader/FragmentClasspath.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/loader/FragmentClasspath.java
new file mode 100644
index 000000000..db25e258c
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/loader/FragmentClasspath.java
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2009 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.baseadaptor.loader;
+
+import java.io.IOException;
+import java.security.ProtectionDomain;
+import org.eclipse.osgi.baseadaptor.BaseData;
+import org.osgi.framework.FrameworkEvent;
+
+/**
+ * A FragmentClasspath contains all the <code>ClasspathEntry</code> objects for a fragment
+ * <code>BaseData</code>.
+ * @since 3.2
+ */
+public class FragmentClasspath {
+ private BaseData bundledata;
+ // Note that PDE has internal dependency on this field type/name (bug 267238)
+ private ClasspathEntry[] entries;
+ private ProtectionDomain domain;
+
+ public FragmentClasspath(BaseData bundledata, ClasspathEntry[] entries, ProtectionDomain domain) {
+ this.bundledata = bundledata;
+ this.entries = entries;
+ this.domain = domain;
+ }
+
+ /**
+ * Returns the fragment BaseData for this FragmentClasspath
+ * @return the fragment BaseData for this FragmentClasspath
+ */
+ public BaseData getBundleData() {
+ return bundledata;
+ }
+
+ /**
+ * Returns the fragment domain for this FragmentClasspath
+ * @return the fragment domain for this FragmentClasspath
+ */
+ public ProtectionDomain getDomain() {
+ return domain;
+ }
+
+ /**
+ * Returns the fragment classpath entries for this FragmentClasspath
+ * @return the fragment classpath entries for this FragmentClasspath
+ */
+ public ClasspathEntry[] getEntries() {
+ return entries;
+ }
+
+ /**
+ * Closes all the classpath entry resources for this FragmentClasspath.
+ *
+ */
+ public void close() {
+ for (int i = 0; i < entries.length; i++) {
+ try {
+ entries[i].getBundleFile().close();
+ } catch (IOException e) {
+ bundledata.getAdaptor().getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, bundledata.getBundle(), e);
+ }
+ }
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/loader/ParallelClassLoader.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/loader/ParallelClassLoader.java
new file mode 100644
index 000000000..f55c2a1c0
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/baseadaptor/loader/ParallelClassLoader.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (c) 2009 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.baseadaptor.loader;
+
+/**
+ * A parallel class loader. Parallel class loaders are thread safe class loaders
+ * which can handle multiple threads loading classes and resources from them at
+ * the same time. This is important for OSGi class loaders because the
+ * class loader delegate in OSGi is not strictly hierarchical, instead the
+ * delegation is grid based and may have cycles.
+ * <p>
+ * The {@link ClasspathManager} handles parallel capable class loaders
+ * differently from other class loaders. For parallel capable
+ * class loaders when {@link ClasspathManager#findLocalClass(String)} is
+ * called a lock will be obtained for the class name being searched while
+ * calling {@link BaseClassLoader#publicFindLoaded(String)} and
+ * {@link BaseClassLoader#defineClass(String, byte[], ClasspathEntry, org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry)}.
+ * This prevents other threads from trying to searching for the same class at the
+ * same time. For other class loaders the class loader lock is obtained
+ * instead. This prevents other threads from trying to search for any
+ * class while the lock is held.
+ * </p>
+ * <p>
+ * <b>Note:</b> This interface is part of an interim API that is still under
+ * development. It is being made available at this early stage to solicit feedback
+ * from pioneering adopters on the understanding that any code that uses this API will may
+ * be broken (repeatedly) as the API evolves.
+ * </p>
+ * @since 3.5
+ */
+public interface ParallelClassLoader extends BaseClassLoader {
+ /**
+ * Indicates if this class loader is parallel capable. Even
+ * if a class loader is able to be parallel capable there are some
+ * restrictions imposed by the VM which may prevent a class loader
+ * from being parallel capable. For example, some VMs may lock
+ * the class loader natively before delegating to a class loader.
+ * This type of locking will prevent a class loader from being
+ * parallel capable.
+ * @return true if this class loader is parallel capable; false otherwise.
+ */
+ boolean isParallelCapable();
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/event/BatchBundleListener.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/event/BatchBundleListener.java
new file mode 100644
index 000000000..a8dd74a8a
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/event/BatchBundleListener.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright (c) 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.event;
+
+import org.osgi.framework.*;
+
+/**
+ * A batch <code>BundleEvent</code> listener.
+ *
+ * <p>
+ * <code>BatchBundleListener</code> is a listener interface that may be
+ * implemented by a bundle developer.
+ * <p>
+ * A <code>BatchBundleListener</code> object is registered with the
+ * Framework using the {@link BundleContext#addBundleListener} method.
+ * <code>BatchBundleListener</code> objects are called with a
+ * <code>BundleEvent</code> object when a bundle has been installed, resolved,
+ * started, stopped, updated, unresolved, or uninstalled.
+ * <p>
+ * A <code>BatchBundleListener</code> acts like a <code>BundleListener</code>
+ * except the framework will call the {@link #batchBegin()} method at the beginning
+ * of a batch process and call the {@link #batchEnd()} at the end of a batch
+ * process. For example, the framework may notify a <code>BatchBundleListener</code>
+ * of a batching process during a refresh packages operation or a resolve bundles
+ * operation.
+ * <p>
+ * During a batching operation the framework will continue to deliver any events using
+ * the {@link BundleListener#bundleChanged(BundleEvent)} method to the
+ * <code>BatchBundleListener</code>. It is the responsiblity of the
+ * <code>BatchBundleListener</code> to decide how to handle events when a
+ * batching operation is in progress.
+ * <p>
+ * Note that the framework does not guarantee that batching operations will not
+ * overlap. This can result in the method {@link #batchBegin()} being called
+ * multiple times before the first {@link #batchEnd()} is called.
+ *
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ * @since 3.1
+ * @see BundleEvent
+ * @see BundleListener
+ */
+public interface BatchBundleListener extends BundleListener {
+ /**
+ * Indicates that a batching process has begun.
+ */
+ public abstract void batchBegin();
+
+ /**
+ * Indicates that a batching process has ended.
+ */
+ public abstract void batchEnd();
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/BundleClassLoader.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/BundleClassLoader.java
new file mode 100644
index 000000000..258b0638f
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/BundleClassLoader.java
@@ -0,0 +1,178 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.adaptor;
+
+import java.io.IOException;
+import java.net.URL;
+import java.security.ProtectionDomain;
+import java.util.*;
+import org.osgi.framework.BundleReference;
+import org.osgi.framework.wiring.BundleWiring;
+
+/**
+ * The BundleClassLoader interface is used by the Framework to load local
+ * classes and resources from a Bundle. Classes that implement this
+ * interface must extend java.lang.ClassLoader, either directly or by extending
+ * a subclass of java.lang.ClassLoader.<p>
+ *
+ * ClassLoaders that implement the <code>BundleClassLoader</code> interface
+ * must use a <code>ClassLoaderDelegate</code> to delegate all class, resource
+ * and native library lookups.
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ * @since 3.1
+ * @see org.eclipse.osgi.framework.adaptor.BundleData#createClassLoader(ClassLoaderDelegate, BundleProtectionDomain, String[])
+ */
+public interface BundleClassLoader /*extends ClassLoader*/extends BundleReference {
+
+ /**
+ * Initializes the ClassLoader. This is called after all currently resolved fragment
+ * bundles have been attached to the BundleClassLoader by the Framework.
+ */
+ public void initialize();
+
+ /**
+ * Finds a local resource in the BundleClassLoader without
+ * consulting the delegate.
+ * @param resource the resource path to find.
+ * @return a URL to the resource or null if the resource does not exist.
+ */
+ public URL findLocalResource(String resource);
+
+ /**
+ * Finds all local resources in the BundleClassLoader with the specified
+ * path without consulting the delegate.
+ * @param resource the resource path to find.
+ * @return An Enumeration of all resources found or null if the resource.
+ * does not exist.
+ */
+ public Enumeration<URL> findLocalResources(String resource);
+
+ /**
+ * Finds a local class in the BundleClassLoader without
+ * consulting the delegate.
+ * @param classname the classname to find.
+ * @return The class object found.
+ * @throws ClassNotFoundException if the classname does not exist locally.
+ */
+ public Class<?> findLocalClass(String classname) throws ClassNotFoundException;
+
+ /**
+ * This method will first search the parent class loader for the resource;
+ * That failing, this method will invoke
+ * {@link ClassLoaderDelegate#findResource(String)} to find the resource.
+ * @param name the resource path to get.
+ * @return a URL for the resource or <code>null</code> if the resource is not found.
+ */
+ public URL getResource(String name);
+
+ /**
+ * This method will first search the parent class loader for the resource;
+ * That failing, this method will invoke
+ * {@link ClassLoaderDelegate#findResource(String)} to find the resource.
+ * @param name the resource path to get.
+ * @return an Enumeration of URL objects for the resource or <code>null</code> if the resource is not found.
+ */
+ public Enumeration<URL> getResources(String name) throws IOException;
+
+ /**
+ * This method will first search the parent class loader for the class;
+ * That failing, this method will invoke
+ * {@link ClassLoaderDelegate#findClass(String)} to find the resource.
+ * @param name the class name to load.
+ * @return the Class.
+ * @throws ClassNotFoundException
+ */
+ public Class<?> loadClass(String name) throws ClassNotFoundException;
+
+ /**
+ * Closes this class loader. After this method is called
+ * loadClass will always throw ClassNotFoundException,
+ * getResource, getResourceAsStream, getResources and will
+ * return null.
+ *
+ */
+ public void close();
+
+ /**
+ * Attaches the BundleData for a fragment to this BundleClassLoader.
+ * The Fragment BundleData resources must be appended to the end of
+ * this BundleClassLoader's classpath. Fragment BundleData resources
+ * must be searched ordered by Bundle ID's.
+ * @param bundledata The BundleData of the fragment.
+ * @param domain The ProtectionDomain of the resources of the fragment.
+ * Any classes loaded from the fragment's BundleData must belong to this
+ * ProtectionDomain.
+ * @param classpath An array of Bundle-ClassPath entries to
+ * use for loading classes and resources. This is specified by the
+ * Bundle-ClassPath manifest entry of the fragment.
+ */
+ public void attachFragment(BundleData bundledata, ProtectionDomain domain, String[] classpath);
+
+ /**
+ * Returns the ClassLoaderDelegate used by this BundleClassLoader
+ * @return the ClassLoaderDelegate used by this BundleClassLoader
+ */
+ public ClassLoaderDelegate getDelegate();
+
+ /**
+ * Returns the parent classloader used by this BundleClassLoader
+ * @return the parent classloader used by this BundleClassLoader
+ */
+ public ClassLoader getParent();
+
+ /**
+ * Returns resource entries for the bundle associated with this class loader.
+ * This is used to answer a call to the
+ * {@link BundleWiring#findEntries(String, String, int)} method.
+ * @param path The path name in which to look.
+ * @param filePattern The file name pattern for selecting resource names in
+ * the specified path.
+ * @param options The options for listing resource names.
+ * @return a list of resource URLs. If no resources are found then
+ * the empty list is returned.
+ * @see {@link BundleWiring#findEntries(String, String, int)}
+ */
+ List<URL> findEntries(String path, String filePattern, int options);
+
+ /**
+ * Returns the names of resources visible to this bundle class loader.
+ * This is used to answer a call to the
+ * {@link BundleWiring#listResources(String, String, int)} method.
+ * This method should simply return the result of calling
+ * {@link ClassLoaderDelegate#listResources(String, String, int)}
+ * @param path The path name in which to look.
+ * @param filePattern The file name pattern for selecting resource names in
+ * the specified path.
+ * @param options The options for listing resource names.
+ * @return a collection of resource names. If no resources are found then
+ * the empty collection is returned.
+ * @see {@link BundleWiring#listResources(String, String, int)}
+ * @see {@link ClassLoaderDelegate#listResources(String, String, int)}
+ */
+ Collection<String> listResources(String path, String filePattern, int options);
+
+ /**
+ * Returns the names of local resources visible to this bundle class loader.
+ * Only the resources available on the local class path of this bundle
+ * class loader are searched.
+ * @param path The path name in which to look.
+ * @param filePattern The file name pattern for selecting resource names in
+ * the specified path.
+ * @param options The options for listing resource names.
+ * @return a collection of resource names. If no resources are found then
+ * the empty collection is returned.
+ * @see {@link ClassLoaderDelegate#listResources(String, String, int)}
+ */
+ Collection<String> listLocalResources(String path, String filePattern, int options);
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/BundleData.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/BundleData.java
new file mode 100644
index 000000000..0dbcd9c34
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/BundleData.java
@@ -0,0 +1,251 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.adaptor;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import org.osgi.framework.*;
+
+/**
+ * The <code>BundleData</code> represents a single bundle that is persistently
+ * stored by a <code>FrameworkAdaptor</code>. A <code>BundleData</code> creates
+ * the ClassLoader for a bundle, finds native libraries installed in the
+ * FrameworkAdaptor for the bundle, creates data files for the bundle,
+ * used to access bundle entries, manifest information, and getting and saving
+ * metadata.
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ * @since 3.1
+ */
+public interface BundleData extends BundleReference {
+
+ /** The BundleData is for a fragment bundle */
+ public static final int TYPE_FRAGMENT = 0x00000001;
+ /** The BundleData is for a framework extension bundle */
+ public static final int TYPE_FRAMEWORK_EXTENSION = 0x00000002;
+ /** The BundleData is for a bootclasspath extension bundle */
+ public static final int TYPE_BOOTCLASSPATH_EXTENSION = 0x00000004;
+ /** The BundleData is for a singleton bundle */
+ public static final int TYPE_SINGLETON = 0x00000008;
+ /** The BundleData is for an extension classpath bundle */
+ public static final int TYPE_EXTCLASSPATH_EXTENSION = 0x00000010;
+
+ /**
+ * Creates the ClassLoader for the BundleData. The ClassLoader created
+ * must use the <code>ClassLoaderDelegate</code> to delegate class, resource
+ * and library loading. The delegate is responsible for finding any resource
+ * or classes imported by the bundle through an imported package or a required
+ * bundle. <p>
+ * The <code>ProtectionDomain</code> domain must be used by the Classloader when
+ * defining a class.
+ * @param delegate The <code>ClassLoaderDelegate</code> to delegate to.
+ * @param domain The <code>BundleProtectionDomain</code> to use when defining a class.
+ * @param bundleclasspath An array of bundle classpaths to use to create this
+ * classloader. This is specified by the Bundle-ClassPath manifest entry.
+ * @return The new ClassLoader for the BundleData.
+ */
+ public BundleClassLoader createClassLoader(ClassLoaderDelegate delegate, BundleProtectionDomain domain, String[] bundleclasspath);
+
+ /**
+ * Gets a <code>URL</code> to the bundle entry specified by path.
+ * This method must not use the BundleClassLoader to find the
+ * bundle entry since the ClassLoader will delegate to find the resource.
+ * @see org.osgi.framework.Bundle#getEntry(String)
+ * @param path The bundle entry path.
+ * @return A URL used to access the entry or null if the entry
+ * does not exist.
+ */
+ public URL getEntry(String path);
+
+ /**
+ * Gets all of the bundle entries that exist under the specified path.
+ * For example: <p>
+ * <code>getEntryPaths("/META-INF")</code> <p>
+ * This will return all entries from the /META-INF directory of the bundle.
+ * @see org.osgi.framework.Bundle#getEntryPaths(String path)
+ * @param path The path to a directory in the bundle.
+ * @return An Enumeration of the entry paths or null if the specified path
+ * does not exist.
+ */
+ public Enumeration<String> getEntryPaths(String path);
+
+ /**
+ * Returns the absolute path name of a native library. The BundleData
+ * ClassLoader invokes this method to locate the native libraries that
+ * belong to classes loaded from this BundleData. Returns
+ * null if the library does not exist in this BundleData.
+ * @param libname The name of the library to find the absolute path to.
+ * @return The absolute path name of the native library or null if
+ * the library does not exist.
+ */
+ public String findLibrary(String libname);
+
+ /**
+ * Installs the native code paths for this BundleData. Each
+ * element of nativepaths must be installed for lookup when findLibrary
+ * is called.
+ * @param nativepaths The array of native code paths to install for
+ * the bundle.
+ * @throws BundleException If any error occurs during install.
+ */
+ public void installNativeCode(String[] nativepaths) throws BundleException;
+
+ /**
+ * Return the bundle data directory.
+ * Attempt to create the directory if it does not exist.
+ *
+ * @see org.osgi.framework.BundleContext#getDataFile(String)
+ * @return Bundle data directory or null if not supported.
+ */
+
+ public File getDataFile(String path);
+
+ /**
+ * Return the Dictionary of manifest headers for the BundleData.
+ * @return Dictionary that contains the Manifest headers for the BundleData.
+ * @throws BundleException if an error occurred while reading the
+ * bundle manifest data.
+ */
+ public Dictionary<String, String> getManifest() throws BundleException;
+
+ /**
+ * Get the BundleData bundle ID. This will be used as the bundle
+ * ID by the framework.
+ * @return The BundleData ID.
+ */
+ public long getBundleID();
+
+ /**
+ * Get the BundleData Location. This will be used as the bundle
+ * location by the framework.
+ * @return the BundleData location.
+ */
+ public String getLocation();
+
+ /**
+ * Get the last time this BundleData was modified.
+ * @return the last time this BundleData was modified
+ */
+ public long getLastModified();
+
+ /**
+ * Close all resources for this BundleData
+ * @throws IOException If an error occurs closing.
+ */
+ public void close() throws IOException;
+
+ /**
+ * Open the BundleData. This method will reopen the BundleData if it has been
+ * previously closed.
+ * @throws IOException If an error occurs opening.
+ */
+ public void open() throws IOException;
+
+ /**
+ * Sets the Bundle object for this BundleData.
+ * @param bundle The Bundle Object for this BundleData.
+ */
+ public void setBundle(Bundle bundle);
+
+ /**
+ * Returns the start level metadata for this BundleData.
+ * @return the start level metadata for this BundleData.
+ */
+ public int getStartLevel();
+
+ /**
+ * Returns the status metadata for this BundleData. A value of 1
+ * indicates that this bundle is started persistently. A value of 0
+ * indicates that this bundle is not started persistently.
+ * @return the status metadata for this BundleData.
+ */
+ public int getStatus();
+
+ /**
+ * Sets the start level metatdata for this BundleData. Metadata must be
+ * stored persistently when BundleData.save() is called.
+ * @param value the start level metadata
+ */
+ public void setStartLevel(int value);
+
+ /**
+ * Sets the status metadata for this BundleData. Metadata must be
+ * stored persistently when BundleData.save() is called.
+ * @param value the status metadata.
+ */
+ public void setStatus(int value);
+
+ /**
+ * Persistently stores all the metadata for this BundleData
+ * @throws IOException
+ */
+ public void save() throws IOException;
+
+ /**
+ * Returns the Bundle-SymbolicName for this BundleData as specified in the bundle
+ * manifest file.
+ * @return the Bundle-SymbolicName for this BundleData.
+ */
+ public String getSymbolicName();
+
+ /**
+ * Returns the Bundle-Version for this BundleData as specified in the bundle
+ * manifest file.
+ * @return the Bundle-Version for this BundleData.
+ */
+ public Version getVersion();
+
+ /**
+ * Returns the type of bundle this BundleData is for.
+ * @return returns the type of bundle this BundleData is for
+ */
+ public int getType();
+
+ /**
+ * Returns the Bundle-ClassPath for this BundleData as specified in
+ * the bundle manifest file.
+ * @return the classpath for this BundleData.
+ */
+ public String[] getClassPath() throws BundleException;
+
+ /**
+ * Returns the Bundle-Activator for this BundleData as specified in
+ * the bundle manifest file.
+ * @return the Bundle-Activator for this BundleData.
+ */
+ public String getActivator();
+
+ /**
+ * Returns the Bundle-RequiredExecutionEnvironment for this BundleData as
+ * specified in the bundle manifest file.
+ * @return the Bundle-RequiredExecutionEnvironment for this BundleData.
+ */
+ public String getExecutionEnvironment();
+
+ /**
+ * Returns the DynamicImport-Package for this BundleData as
+ * specified in the bundle manifest file.
+ * @return the DynamicImport-Packaget for this BundleData.
+ */
+ public String getDynamicImports();
+
+ /**
+ * Finds local resources by searching the class path of this bundle data.
+ * @param path the requested resource name.
+ * @return the requested enumeration of resource URLs or null if the resource does not exist
+ */
+ public Enumeration<URL> findLocalResources(String path);
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/BundleOperation.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/BundleOperation.java
new file mode 100644
index 000000000..da5e17b7b
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/BundleOperation.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.adaptor;
+
+import org.osgi.framework.BundleException;
+
+/**
+ * Bundle Storage interface for managing a persistent storage life
+ * cycle operation upon a bundle.
+ *
+ * <p>This class is used to provide methods to manage a life cycle
+ * operation on a bundle in persistent storage. BundleOperation objects
+ * are returned by the FrameworkAdaptor object and are called by OSGi
+ * to complete the persistent storage life cycle operation.
+ *
+ * <p>For example
+ * <pre>
+ * Bundle bundle;
+ * BundleOperation storage = adaptor.installBundle(location, source);
+ * try {
+ * bundle = storage.begin();
+ *
+ * // Perform some implementation specific work
+ * // which may fail.
+ *
+ * storage.commit(false);
+ * // bundle has been successfully installed
+ * } catch (BundleException e) {
+ * storage.undo();
+ * throw e; // rethrow the error
+ * }
+ * return bundle;
+ * </pre>
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ * @since 3.1
+ */
+public abstract interface BundleOperation {
+
+ /**
+ * Begin the operation on the bundle (install, update, uninstall).
+ *
+ * @return BundleData object for the target bundle.
+ * @throws BundleException If a failure occured modifiying peristent storage.
+ */
+ public abstract BundleData begin() throws BundleException;
+
+ /**
+ * Commit the operation performed.
+ *
+ * @param postpone If true, the bundle's persistent
+ * storage cannot be immediately reclaimed. This may occur if the
+ * bundle is still exporting a package.
+ * @throws BundleException If a failure occured modifiying peristent storage.
+ */
+ public abstract void commit(boolean postpone) throws BundleException;
+
+ /**
+ * Undo the change to persistent storage.
+ * <p>This method can be called before calling commit or if commit
+ * throws an exception to undo any changes in progress.
+ *
+ * @throws BundleException If a failure occured modifiying peristent storage.
+ */
+ public abstract void undo() throws BundleException;
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/BundleProtectionDomain.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/BundleProtectionDomain.java
new file mode 100644
index 000000000..f9a23f7b5
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/BundleProtectionDomain.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2009 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.adaptor;
+
+import java.security.*;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleReference;
+
+/**
+ *
+ * This is a specialized ProtectionDomain for a bundle.
+ * <p>
+ * This class is not intended to be extended by clients.
+ * </p>
+ * @since 3.1
+ */
+public class BundleProtectionDomain extends ProtectionDomain implements BundleReference {
+
+ private volatile Bundle bundle;
+
+ /**
+ * Constructs a special ProtectionDomain for a bundle.
+ *
+ * @param permCollection
+ * the PermissionCollection for the Bundle
+ * @deprecated use {@link #BundleProtectionDomain(PermissionCollection, CodeSource, Bundle)}
+ */
+ public BundleProtectionDomain(PermissionCollection permCollection) {
+ this(permCollection, null, null);
+ }
+
+ /**
+ * Constructs a special ProtectionDomain for a bundle.
+ *
+ * @param permCollection
+ * the PermissionCollection for the Bundle
+ * @param codeSource
+ * the code source for this domain, may be null
+ * @param bundle
+ * the bundle associated with this domain, may be null
+ */
+ public BundleProtectionDomain(PermissionCollection permCollection, CodeSource codeSource, Bundle bundle) {
+ super(codeSource, permCollection);
+ this.bundle = bundle;
+ }
+
+ /**
+ * Sets the bundle object associated with this protection domain.
+ * The bundle can only be set once with either this method or with
+ * the constructor.
+ * @param bundle the bundle object associated with this protection domain
+ */
+ public void setBundle(Bundle bundle) {
+ if (this.bundle != null || bundle == null)
+ return;
+ this.bundle = bundle;
+ }
+
+ public Bundle getBundle() {
+ return bundle;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/BundleWatcher.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/BundleWatcher.java
new file mode 100644
index 000000000..75bb33dc4
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/BundleWatcher.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.framework.adaptor;
+
+import org.osgi.framework.Bundle;
+
+/**
+ * Watches bundle lifecyle processes. This interface is different than that of
+ * a BundleLisener because it gets notified before and after all lifecycle
+ * changes. A bundle watcher acts as the main entry point for logging
+ * bundle activity.
+ * <p>
+ * Note that a bundle watcher is always notified of when a lifecycle processes
+ * has ended even in cases where the lifecycle process may have failed. For
+ * example, if activating a bundle fails the {@link #END_ACTIVATION} flag will
+ * still be sent to the bundle watcher to notify them that the activation
+ * process has ended.
+ * </p>
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ * @since 3.1
+ */
+public interface BundleWatcher {
+ /**
+ * The install process is beginning for a bundle
+ * @since 3.2
+ */
+ public static final int START_INSTALLING = 0x0001;
+ /**
+ * The install process has ended for a bundle
+ * @since 3.2
+ */
+ public static final int END_INSTALLING = 0x0002;
+ /**
+ * The activation process is beginning for a bundle
+ * @since 3.2
+ */
+ public static final int START_ACTIVATION = 0x0004;
+ /**
+ * The activation process has ended for a bundle
+ * @since 3.2
+ */
+ public static final int END_ACTIVATION = 0x0008;
+ /**
+ * The deactivation process is beginning for a bundle
+ * @since 3.2
+ */
+ public static final int START_DEACTIVATION = 0x0010;
+ /**
+ * The deactivation process has ended for a bundle
+ * @since 3.2
+ */
+ public static final int END_DEACTIVATION = 0x0020;
+ /**
+ * The uninstallation process is beginning for a bundle
+ * @since 3.2
+ */
+ public static final int START_UNINSTALLING = 0x0040;
+ /**
+ * The uninstallation process has ended for a bundle
+ * @since 3.2
+ */
+ public static final int END_UNINSTALLING = 0x0080;
+
+ /**
+ * Receives notification that a lifecycle change is going to start or has
+ * ended.
+ * @param bundle the bundle for which the lifecycle change is occurring on.
+ * @param type the type of lifecycle change which is occurring.
+ * @see #START_INSTALLING
+ * @see #END_INSTALLING
+ * @see #START_ACTIVATION
+ * @see #END_ACTIVATION
+ * @see #START_DEACTIVATION
+ * @see #END_DEACTIVATION
+ * @see #START_UNINSTALLING
+ * @see #END_UNINSTALLING
+ * @since 3.2
+ */
+ public void watchBundle(Bundle bundle, int type);
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/ClassLoaderDelegate.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/ClassLoaderDelegate.java
new file mode 100644
index 000000000..fd1ec3d93
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/ClassLoaderDelegate.java
@@ -0,0 +1,134 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.adaptor;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Enumeration;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.wiring.BundleWiring;
+
+/**
+ * A ClassLoaderDelegate is used by the BundleClassLoader in a similar
+ * fashion that a parent ClassLoader is used. A ClassLoaderDelegate must
+ * be queried for any resource or class before it is loaded by the
+ * BundleClassLoader. The Framework implements the ClassLoaderDelegate
+ * and supplies it to the BundleClassLoader. FrameworkAdaptor implementations
+ * are not responsible for suppling an implementation for ClassLoaderDelegate.
+ * <p>
+ * This interface is not intended to be implemented by clients.
+ * </p>
+ * @noimplement This interface is not intended to be implemented by clients.
+ * @since 3.1
+ */
+public interface ClassLoaderDelegate {
+ /**
+ * Finds a class for a bundle that may be outside of the actual bundle
+ * (i.e. a class from an imported package or required bundle).<p>
+ *
+ * If the class does not belong to an imported package or is not
+ * found in a required bundle then the ClassloaderDelegate will call
+ * BundleClassLoader.findLocalClass(). <p>
+ *
+ * If no class is found then a ClassNotFoundException is thrown.
+ * @param classname the class to find.
+ * @return the Class.
+ * @throws ClassNotFoundException if the class is not found.
+ */
+ public Class<?> findClass(String classname) throws ClassNotFoundException;
+
+ /**
+ * Finds a resource for a bundle that may be outside of the actual bundle
+ * (i.e. a resource from an imported package or required bundle).<p>
+ *
+ * If the resource does not belong to an imported package or is not
+ * found in a required bundle then the ClassloaderDelegate will call
+ * BundleClassLoader.findLocalResource(). <p>
+ *
+ * If no resource is found then return null.
+ * @param resource the resource to load.
+ * @return the resource or null if resource is not found.
+ */
+ public URL findResource(String resource);
+
+ /**
+ * Finds an enumeration of resources for a bundle that may be outside of
+ * the actual bundle (i.e. a resource from an imported package or required
+ * bundle).<p>
+ *
+ * If the resource does not belong to an imported package or is not
+ * found in a required bundle then the ClassloaderDelegate will call
+ * BundleClassLoader.findLocalResource(). <p>
+ * If no resource is found then return null.
+ * @param resource the resource to find.
+ * @return the enumeration of resources found or null if the resource
+ * does not exist.
+ */
+ public Enumeration<URL> findResources(String resource) throws IOException;
+
+ /**
+ * Returns the absolute path name of a native library. The following is
+ * a list of steps that a ClassLoaderDelegate must take when trying to
+ * find a library:
+ * <ul>
+ * <li>If the bundle is a fragment then try to find the library in the
+ * host bundle.
+ * <li>if the bundle is a host then try to find the library in the
+ * host bundle and then try to find the library in the fragment
+ * bundles.
+ * </ul>
+ * If no library is found return null.
+ * @param libraryname the library to find the path to.
+ * @return the path to the library or null if not found.
+ */
+ public String findLibrary(String libraryname);
+
+ /**
+ * Returns true if the lazy trigger has been set for this
+ * delegate. The lazy trigger is set when a bundle has been
+ * marked for lazy activation due to a successful class load.
+ * @return true if the lazy trigger has been set
+ * @since 3.6
+ */
+ public boolean isLazyTriggerSet();
+
+ /**
+ * Sets the lazy trigger for this delegate. This will activate
+ * the bundle if the bundle has been started with the activation
+ * policy and the bundle's start level is met.
+ * @throws BundleException if an error occurred while activating the bundle
+ * @see ClassLoaderDelegate#isLazyTriggerSet()
+ * @since 3.6
+ */
+ public void setLazyTrigger() throws BundleException;
+
+ /**
+ * Returns the names of resources visible to this delegate.
+ * This is used to answer a call to the
+ * {@link BundleWiring#listResources(String, String, int)} method.
+ * First a search is done on the packages imported by the bundle associated
+ * with this delegate. Next a search is done on the the bundles required by
+ * the bundle associated with this delegate. Finally a local search of
+ * the bundle associated with this delegate is done by calling
+ * {@link BundleClassLoader#listLocalResources(String, String, int)}. Note
+ * that for imported packages the search stops at the source for the import.
+ * @param path The path name in which to look.
+ * @param filePattern The file name pattern for selecting resource names in
+ * the specified path.
+ * @param options The options for listing resource names.
+ * @return a collection of resource names. If no resources are found then
+ * the empty collection is returned.
+ * @see BundleWiring#listResources(String, String, int)
+ */
+ public Collection<String> listResources(String path, String filePattern, int options);
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/ClassLoaderDelegateHook.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/ClassLoaderDelegateHook.java
new file mode 100644
index 000000000..424feb98a
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/ClassLoaderDelegateHook.java
@@ -0,0 +1,124 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.adaptor;
+
+import java.io.FileNotFoundException;
+import java.net.URL;
+import java.util.Enumeration;
+import org.eclipse.osgi.baseadaptor.HookRegistry;
+
+/**
+ * A ClassLoaderDelegateHook hooks into the <code>ClassLoaderDelegate</code>.
+ * @see ClassLoaderDelegate
+ * @see HookRegistry#getClassLoaderDelegateHooks()
+ * @see HookRegistry#addClassLoaderDelegateHook(ClassLoaderDelegateHook)
+ * @since 3.4
+ */
+public interface ClassLoaderDelegateHook {
+ /**
+ * Called by a {@link ClassLoaderDelegate#findClass(String)} method before delegating to the resolved constraints and
+ * local bundle for a class load. If this method returns null then normal delegation is done. If this method
+ * returns a non-null value then the rest of the delegation process is skipped and the returned value is used.
+ * If this method throws a <code>ClassNotFoundException</code> then the calling
+ * {@link ClassLoaderDelegate#findClass(String)} method re-throws the exception.
+ * @param name the name of the class to find
+ * @param classLoader the bundle class loader
+ * @param data the bundle data
+ * @return the class found by this hook or null if normal delegation should continue
+ * @throws ClassNotFoundException to terminate the delegation and throw an exception
+ */
+ public Class<?> preFindClass(String name, BundleClassLoader classLoader, BundleData data) throws ClassNotFoundException;
+
+ /**
+ * Called by a {@link ClassLoaderDelegate#findClass(String)} method after delegating to the resolved constraints and
+ * local bundle for a class load. This method will only be called if no class was found
+ * from the normal delegation.
+ * @param name the name of the class to find
+ * @param classLoader the bundle class loader
+ * @param data the bundle data
+ * @return the class found by this hook or null if normal delegation should continue
+ * @throws ClassNotFoundException to terminate the delegation and throw an exception
+ */
+ public Class<?> postFindClass(String name, BundleClassLoader classLoader, BundleData data) throws ClassNotFoundException;
+
+ /**
+ * Called by a {@link ClassLoaderDelegate #findResource(String)} before delegating to the resolved constraints and
+ * local bundle for a resource load. If this method returns null then normal delegation is done.
+ * If this method returns a non-null value then the rest of the delegation process is skipped and the returned value is used.
+ * If this method throws an <code>FileNotFoundException</code> then the delegation is terminated.
+ * @param name the name of the resource to find
+ * @param classLoader the bundle class loader
+ * @param data the bundle data
+ * @return the resource found by this hook or null if normal delegation should continue
+ * @throws FileNotFoundException to terminate the delegation
+ */
+ public URL preFindResource(String name, BundleClassLoader classLoader, BundleData data) throws FileNotFoundException;
+
+ /**
+ * Called by a {@link ClassLoaderDelegate} after delegating to the resolved constraints and
+ * local bundle for a resource load. This method will only be called if no resource was found
+ * from the normal delegation.
+ * @param name the name of the resource to find
+ * @param classLoader the bundle class loader
+ * @param data the bundle data
+ * @return the resource found by this hook or null if normal delegation should continue
+ * @throws FileNotFoundException to terminate the delegation
+ */
+ public URL postFindResource(String name, BundleClassLoader classLoader, BundleData data) throws FileNotFoundException;
+
+ /**
+ * Called by a {@link ClassLoaderDelegate} before delegating to the resolved constraints and
+ * local bundle for a resource load. If this method returns null then normal delegation is done.
+ * If this method returns a non-null value then the rest of the delegation process is skipped and the returned value is used.
+ * If this method throws an <code>FileNotFoundException</code> then the delegation is terminated
+ * @param name the name of the resource to find
+ * @param classLoader the bundle class loader
+ * @param data the bundle data
+ * @return the resources found by this hook or null if normal delegation should continue
+ * @throws FileNotFoundException to terminate the delegation
+ */
+ public Enumeration<URL> preFindResources(String name, BundleClassLoader classLoader, BundleData data) throws FileNotFoundException;
+
+ /**
+ * Called by a {@link ClassLoaderDelegate} after delegating to the resolved constraints and
+ * local bundle for a resource load. This method will only be called if no resources were found
+ * from the normal delegation.
+ * @param name the name of the resource to find
+ * @param classLoader the bundle class loader
+ * @param data the bundle data
+ * @return the resources found by this hook or null if normal delegation should continue
+ * @throws FileNotFoundException to terminate the delegation
+ */
+ public Enumeration<URL> postFindResources(String name, BundleClassLoader classLoader, BundleData data) throws FileNotFoundException;
+
+ /**
+ * Called by a {@link ClassLoaderDelegate} before normal delegation. If this method returns
+ * a non-null value then the rest of the delegation process is skipped and the returned value
+ * is used.
+ * @param name the name of the library to find
+ * @param classLoader the bundle class loader
+ * @param data the bundle data
+ * @return the library found by this hook or null if normal delegation should continue
+ * @throws FileNotFoundException to terminate the delegation
+ */
+ public String preFindLibrary(String name, BundleClassLoader classLoader, BundleData data) throws FileNotFoundException;
+
+ /**
+ * Called by a {@link ClassLoaderDelegate} after normal delegation. This method will only be called
+ * if no library was found from the normal delegation.
+ * @param name the name of the library to find
+ * @param classLoader the bundle class loader
+ * @param data the bundle data
+ * @return the library found by this hook or null if normal delegation should continue
+ */
+ public String postFindLibrary(String name, BundleClassLoader classLoader, BundleData data);
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/EventPublisher.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/EventPublisher.java
new file mode 100644
index 000000000..429848723
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/EventPublisher.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.adaptor;
+
+import org.osgi.framework.Bundle;
+
+/**
+ * The EventPublisher is used by FrameworkAdaptors to publish events to the
+ * Framework.
+ * <p>
+ * This interface is not intended to be implemented by clients.
+ * </p>
+ * @since 3.1
+ */
+public interface EventPublisher {
+
+ /**
+ * Publish a FrameworkEvent.
+ *
+ * @param type FrameworkEvent type.
+ * @param bundle Bundle related to FrameworkEven or <tt>null</tt> to for the
+ * system bundle.
+ * @param throwable Related exception or <tt>null</tt>.
+ * @see org.osgi.framework.FrameworkEvent
+ */
+ public abstract void publishFrameworkEvent(int type, Bundle bundle, Throwable throwable);
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/FilePath.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/FilePath.java
new file mode 100644
index 000000000..55dd1d39c
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/FilePath.java
@@ -0,0 +1,257 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.framework.adaptor;
+
+import java.io.File;
+
+/**
+ * A utility class for manipulating file system paths.
+ * <p>
+ * This class is not intended to be subclassed by clients but
+ * may be instantiated.
+ * </p>
+ *
+ * @since 3.1
+ */
+public class FilePath {
+ // Constant value indicating if the current platform is Windows
+ private static final boolean WINDOWS = java.io.File.separatorChar == '\\';
+ private final static String CURRENT_DIR = "."; //$NON-NLS-1$
+ // Device separator character constant ":" used in paths.
+ private static final char DEVICE_SEPARATOR = ':';
+ private static final byte HAS_LEADING = 1;
+ private static final byte HAS_TRAILING = 4;
+ // Constant value indicating no segments
+ private static final String[] NO_SEGMENTS = new String[0];
+ private final static String PARENT_DIR = ".."; //$NON-NLS-1$
+ private final static char SEPARATOR = '/';
+ private final static String UNC_SLASHES = "//"; //$NON-NLS-1$
+ // if UNC, device will be \\host\share, otherwise, it will be letter/name + colon
+ private String device;
+ private byte flags;
+ private String[] segments;
+
+ /**
+ * Constructs a new file path from the given File object.
+ *
+ * @param location
+ */
+ public FilePath(File location) {
+ initialize(location.getPath());
+ if (location.isDirectory())
+ flags |= HAS_TRAILING;
+ else
+ flags &= ~HAS_TRAILING;
+ }
+
+ /**
+ * Constructs a new file path from the given string path.
+ *
+ * @param original
+ */
+ public FilePath(String original) {
+ initialize(original);
+ }
+
+ /*
+ * Returns the number of segments in the given path
+ */
+ private int computeSegmentCount(String path) {
+ int len = path.length();
+ if (len == 0 || (len == 1 && path.charAt(0) == SEPARATOR))
+ return 0;
+ int count = 1;
+ int prev = -1;
+ int i;
+ while ((i = path.indexOf(SEPARATOR, prev + 1)) != -1) {
+ if (i != prev + 1 && i != len)
+ ++count;
+ prev = i;
+ }
+ if (path.charAt(len - 1) == SEPARATOR)
+ --count;
+ return count;
+ }
+
+ /*
+ * Splits the given path string into an array of segments.
+ */
+ private String[] computeSegments(String path) {
+ int maxSegmentCount = computeSegmentCount(path);
+ if (maxSegmentCount == 0)
+ return NO_SEGMENTS;
+ String[] newSegments = new String[maxSegmentCount];
+ int len = path.length();
+ // allways absolute
+ int firstPosition = isAbsolute() ? 1 : 0;
+ int lastPosition = hasTrailingSlash() ? len - 2 : len - 1;
+ // for non-empty paths, the number of segments is
+ // the number of slashes plus 1, ignoring any leading
+ // and trailing slashes
+ int next = firstPosition;
+ int actualSegmentCount = 0;
+ for (int i = 0; i < maxSegmentCount; i++) {
+ int start = next;
+ int end = path.indexOf(SEPARATOR, next);
+ next = end + 1;
+ String segment = path.substring(start, end == -1 ? lastPosition + 1 : end);
+ if (CURRENT_DIR.equals(segment))
+ continue;
+ if (PARENT_DIR.equals(segment)) {
+ if (actualSegmentCount > 0)
+ actualSegmentCount--;
+ continue;
+ }
+ newSegments[actualSegmentCount++] = segment;
+ }
+ if (actualSegmentCount == newSegments.length)
+ return newSegments;
+ if (actualSegmentCount == 0)
+ return NO_SEGMENTS;
+ String[] actualSegments = new String[actualSegmentCount];
+ System.arraycopy(newSegments, 0, actualSegments, 0, actualSegments.length);
+ return actualSegments;
+ }
+
+ /**
+ * Returns the device for this file system path, or <code>null</code> if
+ * none exists. The device string ends with a colon.
+ *
+ * @return the device string or null
+ */
+ public String getDevice() {
+ return device;
+ }
+
+ /**
+ * Returns the segments in this path. If this path has no segments, returns an empty array.
+ *
+ * @return an array containing all segments for this path
+ */
+ public String[] getSegments() {
+ return segments.clone();
+ }
+
+ /**
+ * Returns whether this path ends with a slash.
+ *
+ * @return <code>true</code> if the path ends with a slash, false otherwise
+ */
+ public boolean hasTrailingSlash() {
+ return (flags & HAS_TRAILING) != 0;
+ }
+
+ private void initialize(String original) {
+ original = original.indexOf('\\') == -1 ? original : original.replace('\\', SEPARATOR);
+ if (WINDOWS) {
+ // only deal with devices/UNC paths on Windows
+ int deviceSeparatorPos = original.indexOf(DEVICE_SEPARATOR);
+ if (deviceSeparatorPos >= 0) {
+ //extract device if any
+ //remove leading slash from device part to handle output of URL.getFile()
+ int start = original.charAt(0) == SEPARATOR ? 1 : 0;
+ device = original.substring(start, deviceSeparatorPos + 1);
+ original = original.substring(deviceSeparatorPos + 1, original.length());
+ } else if (original.startsWith(UNC_SLASHES)) {
+ // handle UNC paths
+ int uncPrefixEnd = original.indexOf(SEPARATOR, 2);
+ if (uncPrefixEnd >= 0)
+ uncPrefixEnd = original.indexOf(SEPARATOR, uncPrefixEnd + 1);
+ if (uncPrefixEnd >= 0) {
+ device = original.substring(0, uncPrefixEnd);
+ original = original.substring(uncPrefixEnd, original.length());
+ } else
+ // not a valid UNC
+ throw new IllegalArgumentException("Not a valid UNC: " + original); //$NON-NLS-1$
+ }
+ }
+ // device names letters and UNCs properly stripped off
+ if (original.charAt(0) == SEPARATOR)
+ flags |= HAS_LEADING;
+ if (original.charAt(original.length() - 1) == SEPARATOR)
+ flags |= HAS_TRAILING;
+ segments = computeSegments(original);
+ }
+
+ /**
+ * Returns whether this path is absolute (begins with a slash).
+ *
+ * @return <code>true</code> if this path is absolute, <code>false</code> otherwise
+ */
+ public boolean isAbsolute() {
+ return (flags & HAS_LEADING) != 0;
+ }
+
+ /**
+ * Returns a string representing this path as a relative to the given base path.
+ * <p>
+ * If this path and the given path do not use the same device letter, this path's
+ * string representation is returned as is.
+ * </p>
+ *
+ * @param base the path this path should be made relative to
+ * @return a string representation for this path as relative to the given base path
+ */
+ public String makeRelative(FilePath base) {
+ if (base.device != null && !base.device.equalsIgnoreCase(this.device))
+ return base.toString();
+ int baseCount = this.segments.length;
+ int count = this.matchingFirstSegments(base);
+ if (baseCount == count && count == base.segments.length)
+ return base.hasTrailingSlash() ? ("." + SEPARATOR) : "."; //$NON-NLS-1$ //$NON-NLS-2$
+ StringBuffer relative = new StringBuffer(); //
+ for (int j = 0; j < baseCount - count; j++)
+ relative.append(PARENT_DIR + SEPARATOR);
+ for (int i = 0; i < base.segments.length - count; i++) {
+ relative.append(base.segments[count + i]);
+ relative.append(SEPARATOR);
+ }
+ if (!base.hasTrailingSlash())
+ relative.deleteCharAt(relative.length() - 1);
+ return relative.toString();
+ }
+
+ /*
+ * Returns the number of segments in this matching the first segments of the
+ * given path.
+ */
+ private int matchingFirstSegments(FilePath anotherPath) {
+ int anotherPathLen = anotherPath.segments.length;
+ int max = Math.min(segments.length, anotherPathLen);
+ int count = 0;
+ for (int i = 0; i < max; i++) {
+ if (!segments[i].equals(anotherPath.segments[i]))
+ return count;
+ count++;
+ }
+ return count;
+ }
+
+ /**
+ * Returns a string representation of this path.
+ *
+ * @return a string representation of this path
+ */
+ public String toString() {
+ StringBuffer result = new StringBuffer();
+ if (device != null)
+ result.append(device);
+ if (isAbsolute())
+ result.append(SEPARATOR);
+ for (int i = 0; i < segments.length; i++) {
+ result.append(segments[i]);
+ result.append(SEPARATOR);
+ }
+ if (segments.length > 0 && !hasTrailingSlash())
+ result.deleteCharAt(result.length() - 1);
+ return result.toString();
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/FrameworkAdaptor.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/FrameworkAdaptor.java
new file mode 100644
index 000000000..e09b3633c
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/FrameworkAdaptor.java
@@ -0,0 +1,296 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.adaptor;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.*;
+import org.eclipse.osgi.framework.log.FrameworkLog;
+import org.eclipse.osgi.service.resolver.PlatformAdmin;
+import org.eclipse.osgi.service.resolver.State;
+import org.osgi.framework.*;
+
+/**
+ * FrameworkAdaptor interface to the osgi framework. This class is used to provide
+ * platform specific support for the osgi framework.
+ *
+ * <p>The OSGi framework will call this class to perform platform specific functions.
+ *
+ * Classes that implement FrameworkAdaptor MUST provide a constructor that takes as a
+ * parameter an array of Strings. This array will contain arguments to be
+ * handled by the FrameworkAdaptor. The FrameworkAdaptor implementation may define the format
+ * and content of its arguments.
+ *
+ * The constructor should parse the arguments passed to it and remember them.
+ * The initialize method should perform the actual processing of the adaptor
+ * arguments.
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ * @since 3.1
+ */
+
+public interface FrameworkAdaptor {
+
+ public static final String FRAMEWORK_SYMBOLICNAME = "org.eclipse.osgi"; //$NON-NLS-1$
+
+ /**
+ * Initialize the FrameworkAdaptor object so that it is ready to be
+ * called by the framework. Handle the arguments that were
+ * passed to the constructor.
+ * This method must be called before any other FrameworkAdaptor methods.
+ * @param eventPublisher The EventPublisher used to publish any events to
+ * the framework.
+ */
+ public void initialize(EventPublisher eventPublisher);
+
+ /**
+ * Initialize the persistent storage for the adaptor.
+ *
+ * @throws IOException If the adaptor is unable to
+ * initialize the bundle storage.
+ */
+ public void initializeStorage() throws IOException;
+
+ /**
+ * Compact/cleanup the persistent storage for the adaptor.
+ * @throws IOException If the adaptor is unable to
+ * compact the bundle storage.
+ *
+ */
+ public void compactStorage() throws IOException;
+
+ /**
+ * Return the properties object for the adaptor.
+ * The properties in the returned object supplement
+ * the System properties.
+ * The framework may modify this object. The Framework
+ * will use the returned properties to set the System
+ * properties.
+ *
+ * @return The properties object for the adaptor.
+ */
+ public Properties getProperties();
+
+ /**
+ * Return a list of the installed bundles. Each element in the
+ * list must be of type <code>BundleData</code>. Each <code>BundleData</code>
+ * corresponds to one bundle that is persistently stored.
+ * This method must construct <code>BundleData</code> objects for all
+ * installed bundles and return an array containing the objects.
+ * The returned array becomes the property of the framework.
+ *
+ * @return Array of installed BundleData objects, or null if none can be found.
+ */
+ public BundleData[] getInstalledBundles();
+
+ /**
+ * Map a location to a URLConnection. This is used by the Framework when installing a bundle
+ * from a spacified location.
+ *
+ * @param location of the bundle.
+ * @return URLConnection that represents the location.
+ * @throws BundleException if the mapping fails.
+ */
+ public URLConnection mapLocationToURLConnection(String location) throws BundleException;
+
+ /**
+ * Prepare to install a bundle from a URLConnection.
+ * <p>To complete the install,
+ * begin and then commit
+ * must be called on the returned <code>BundleOperation</code> object.
+ * If either of these methods throw a BundleException
+ * or some other error occurs,
+ * then undo must be called on the <code>BundleOperation</code> object
+ * to undo the change to persistent storage.
+ *
+ * @param location Bundle location.
+ * @param source URLConnection from which the bundle may be read.
+ * Any InputStreams returned from the source
+ * (URLConnections.getInputStream) must be closed by the
+ * <code>BundleOperation</code> object.
+ * @return BundleOperation object to be used to complete the install.
+ */
+ public BundleOperation installBundle(String location, URLConnection source);
+
+ /**
+ * Prepare to update a bundle from a URLConnection.
+ * <p>To complete the update
+ * begin and then commit
+ * must be called on the returned <code>BundleOperation</code> object.
+ * If either of these methods throw a BundleException
+ * or some other error occurs,
+ * then undo must be called on the <code>BundleOperation</code> object
+ * to undo the change to persistent storage.
+ *
+ * @param bundledata BundleData to update.
+ * @param source URLConnection from which the updated bundle may be read.
+ * Any InputStreams returned from the source
+ * (URLConnections.getInputStream) must be closed by the
+ * <code>BundleOperation</code> object.
+ * @return BundleOperation object to be used to complete the update.
+ */
+ public BundleOperation updateBundle(BundleData bundledata, URLConnection source);
+
+ /**
+ * Prepare to uninstall a bundle.
+ * <p>To complete the uninstall,
+ * begin and then commit
+ * must be called on the returned <code>BundleOperation</code> object.
+ * If either of these methods throw a BundleException
+ * or some other error occurs,
+ * then undo must be called on the <code>BundleOperation</code> object
+ * to undo the change to persistent storage.
+ *
+ * @param bundledata BundleData to uninstall.
+ * @return BundleOperation object to be used to complete the uninstall.
+ */
+ public BundleOperation uninstallBundle(BundleData bundledata);
+
+ /**
+ * Returns the total amount of free space available for bundle storage on the device.
+ *
+ * @return Free space available in bytes or -1 if it does not apply to this adaptor
+ * @exception IOException if an I/O error occurs determining the available space
+ */
+ public long getTotalFreeSpace() throws IOException;
+
+ /**
+ * Returns the PermissionStorage object which will be used to
+ * to manage the permission data.
+ *
+ * @return The PermissionStorage object for the adaptor.
+ * @see "org.osgi.service.permissionadmin.PermissionAdmin"
+ */
+ public PermissionStorage getPermissionStorage() throws IOException;
+
+ /**
+ * The framework will call this method after the
+ * System BundleActivator.start(BundleContext) has been called. The context is
+ * the System Bundle's BundleContext. This method allows FrameworkAdaptors to
+ * have access to the OSGi framework to get services, register services and
+ * perform other OSGi operations.
+ * @param context The System Bundle's BundleContext.
+ * @exception BundleException on any error that may occur.
+ */
+ public void frameworkStart(BundleContext context) throws BundleException;
+
+ /**
+ * The framework will call this method before the
+ * System BundleActivator.stop(BundleContext) has been called. The context is
+ * the System Bundle's BundleContext. This method allows FrameworkAdaptors to
+ * have access to the OSGi framework to get services, register services and
+ * perform other OSGi operations.
+ * @param context The System Bundle's BundleContext.
+ * @exception BundleException on any error that may occur.
+ */
+ public void frameworkStop(BundleContext context) throws BundleException;
+
+ /**
+ * The framework will call this method before the process of framework
+ * shutdown is started. This gives FrameworkAdaptors a chance to
+ * perform actions before the framework start level is decremented and
+ * all the bundles are stopped. This method will get called before the
+ * {@link #frameworkStop(BundleContext)} method.
+ * @param context The System Bundle's BundleContext.
+ */
+ public void frameworkStopping(BundleContext context);
+
+ /**
+ * Returns the initial bundle start level as maintained by this adaptor
+ * @return the initial bundle start level
+ */
+ public int getInitialBundleStartLevel();
+
+ /**
+ * Sets the initial bundle start level
+ * @param value the initial bundle start level
+ */
+ public void setInitialBundleStartLevel(int value);
+
+ /**
+ * Returns the FrameworkLog for the FrameworkAdaptor. The FrameworkLog
+ * is used by the Framework and FrameworkAdaptor to log any error messages
+ * and FramworkEvents of type ERROR.
+ * @return The FrameworkLog to be used by the Framework.
+ */
+ public FrameworkLog getFrameworkLog();
+
+ /**
+ * Creates a BundleData object for the System Bundle. The BundleData
+ * returned will be used to define the System Bundle for the Framework.
+ * @return the BundleData for the System Bundle.
+ * @throws BundleException if any error occurs while creating the
+ * System BundleData.
+ */
+ public BundleData createSystemBundleData() throws BundleException;
+
+ /**
+ * Returns the bundle watcher for this FrameworkAdaptor.
+ * @return the bundle watcher for this FrameworkAdaptor.
+ */
+ public BundleWatcher getBundleWatcher();
+
+ /**
+ * Returns the PlatformAdmin for this FrameworkAdaptor.
+ * <p>
+ * This method will not be called until after
+ * {@link FrameworkAdaptor#frameworkStart(BundleContext)} is called.
+ * @return the PlatformAdmin for this FrameworkAdaptor.
+ */
+ public PlatformAdmin getPlatformAdmin();
+
+ /**
+ * Returns the State for this FrameworkAdaptor.
+ * <p>
+ * This method will not be called until after
+ * {@link FrameworkAdaptor#frameworkStart(BundleContext)} is called.
+ * The State returned is used by the framework to resolve bundle
+ * dependencies.
+ * @return the State for this FrameworkAdaptor.
+ */
+ public State getState();
+
+ /**
+ * Returns the parent ClassLoader all BundleClassLoaders created. All
+ * BundleClassLoaders that are created must use the ClassLoader returned
+ * by this method as a parent ClassLoader. Each call to this method must
+ * return the same ClassLoader object.
+ * @return the parent ClassLoader for all BundleClassLoaders created.
+ */
+ public ClassLoader getBundleClassLoaderParent();
+
+ /**
+ * Handles a RuntimeException or Error that was caught by the Framework and
+ * there is not a suitable caller to propagate the Throwable to. This gives
+ * the FrameworkAdaptor the ablity to handle such cases. For example, a
+ * FrameworkAdaptor may decide that such unexpected errors should cause an error
+ * message to be logged, or that the Framework should be terminated immediately.
+ * @param error The Throwable for the runtime error that is to be handled.
+ */
+ public void handleRuntimeError(Throwable error);
+
+ /**
+ * Returns resources entries for the specified bundle datas.
+ * @param datas the list of bundle datas to search in
+ * @param path The path name in which to look.
+ * @param filePattern The file name pattern for selecting resource names in
+ * the specified path.
+ * @param options The options for listing resource names.
+ * @return a list of resource URLs. If no resources are found then
+ * the empty list is returned.
+ * @see BundleClassLoader#findEntries(String, String, int)
+ * @see Bundle#findEntries(String, String, boolean)
+ */
+ public Enumeration<URL> findEntries(List<BundleData> datas, String path, String filePattern, int options);
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/PermissionStorage.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/PermissionStorage.java
new file mode 100644
index 000000000..6ad1d04b4
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/PermissionStorage.java
@@ -0,0 +1,100 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.adaptor;
+
+import java.io.IOException;
+
+/**
+ * Permission Storage interface for managing a persistent storage of
+ * bundle permissions.
+ *
+ * <p>This class is used to provide methods to manage
+ * persistent storage of bundle permissions. The PermissionStorage object
+ * is returned by the FrameworkAdaptor object and is called
+ * to persistently store bundle permissions.
+ *
+ * <p>The permission data will typically take the form of encoded
+ * <tt>PermissionInfo</tt> Strings.
+ * See org.osgi.service.permissionadmin.PermissionInfo.
+ *
+ * <p>For example
+ * <pre>
+ * PermissionStorage storage = adaptor.getPermissionStorage();
+ * try {
+ * storage.setPermissionData(location, permissions);
+ * } catch (IOException e) {
+ * // Take some error action.
+ * }
+ * </pre>
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ * @since 3.1
+ */
+public interface PermissionStorage {
+ /**
+ * Returns the locations that have permission data assigned to them,
+ * that is, locations for which permission data
+ * exists in persistent storage.
+ *
+ * @return The locations that have permission data in
+ * persistent storage, or <tt>null</tt> if there is no permission data
+ * in persistent storage.
+ * @throws IOException If a failure occurs accessing persistent storage.
+ */
+ public String[] getLocations() throws IOException;
+
+ /**
+ * Gets the permission data assigned to the specified
+ * location.
+ *
+ * @param location The location whose permission data is to
+ * be returned.
+ * The location can be <tt>null</tt> for the default permission data.
+ *
+ * @return The permission data assigned to the specified
+ * location, or <tt>null</tt> if that location has not been assigned any
+ * permission data.
+ * @throws IOException If a failure occurs accessing persistent storage.
+ */
+ public String[] getPermissionData(String location) throws IOException;
+
+ /**
+ * Assigns the specified permission data to the specified
+ * location.
+ *
+ * @param location The location that will be assigned the
+ * permissions.
+ * The location can be <tt>null</tt> for the default permission data.
+ * @param data The permission data to be assigned, or <tt>null</tt>
+ * if the specified location is to be removed from persistent storaqe.
+ * @throws IOException If a failure occurs modifying persistent storage.
+ */
+ public void setPermissionData(String location, String[] data) throws IOException;
+
+ /**
+ * Persists the array of encoded ConditionalPermissionInfo strings
+ * @param infos an array of encoded ConditionalPermissionInfo strings
+ * @throws IOException If a failure occurs modifying persistent storage.
+ * @since 3.2
+ */
+ public void saveConditionalPermissionInfos(String[] infos) throws IOException;
+
+ /**
+ * Returns the persistent array of encoded ConditionalPermissionInfo strings
+ * @return an array of encoded ConditionalPermissionInfo strings or null
+ * if none exist in persistent storage.
+ * @throws IOException If a failure occurs accessing persistent storage.
+ * @since 3.2
+ */
+ public String[] getConditionalPermissionInfos() throws IOException;
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/StatusException.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/StatusException.java
new file mode 100644
index 000000000..86dfd3090
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/adaptor/StatusException.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.framework.adaptor;
+
+/**
+ * An exception my implement the StatusException interface to give more status information about the type of exception.
+ */
+public interface StatusException {
+ /**
+ * The exception is ok and expected
+ */
+ public static final int CODE_OK = 0x01;
+ /**
+ * The exception is for informational purposes
+ */
+ public static final int CODE_INFO = 0x02;
+ /**
+ * The exception is unexpected by may be handled, but a warning should be logged
+ */
+ public static final int CODE_WARNING = 0x04;
+ /**
+ * The exception is unexpected and should result in an error.
+ */
+ public static final int CODE_ERROR = 0x08;
+
+ /**
+ * Returns the status object
+ * @return the status object
+ */
+ public Object getStatus();
+
+ /**
+ * Returns the status code
+ * @return the status code
+ */
+ public int getStatusCode();
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/console/CommandInterpreter.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/console/CommandInterpreter.java
new file mode 100644
index 000000000..627d98180
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/console/CommandInterpreter.java
@@ -0,0 +1,91 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.console;
+
+import java.util.Dictionary;
+import org.osgi.framework.Bundle;
+
+/**
+ * A command interpreter is a shell that can interpret command
+ * lines. This object is passed as parameter when a CommandProvider
+ * is invoked.
+ * <p>
+ * This interface is not intended to be implemented by clients.
+ * </p>
+ * @since 3.1
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface CommandInterpreter {
+ /**
+ * Get the next argument in the input. If no arguments are left then null is returned.
+ *
+ * E.g. if the commandline is hello world, the _hello method
+ * will get "world" as the first argument.
+ * @return the next argument or null if no arguments are left.
+ */
+ public String nextArgument();
+
+ /**
+ * Execute a command line as if it came from the end user
+ * and return the result.
+ * @param cmd The command line to execute.
+ * @return the result of the command.
+ */
+ public Object execute(String cmd);
+
+ /**
+ * Prints an object to the outputstream
+ *
+ * @param o the object to be printed
+ */
+ public void print(Object o);
+
+ /**
+ * Prints an empty line to the outputstream
+ */
+ public void println();
+
+ /**
+ * Prints an object to the output medium (appended with newline character).
+ * <p>
+ * If running on the target environment the user is prompted with '--more'
+ * if more than the configured number of lines have been printed without user prompt.
+ * That way the user of the program has control over the scrolling.
+ * <p>
+ * For this to work properly you should not embedded "\n" etc. into the string.
+ *
+ * @param o the object to be printed
+ */
+ public void println(Object o);
+
+ /**
+ * Print a stack trace including nested exceptions.
+ * @param t The offending exception
+ */
+ public void printStackTrace(Throwable t);
+
+ /**
+ * Prints the given dictionary sorted by keys.
+ *
+ * @param dic the dictionary to print
+ * @param title the header to print above the key/value pairs
+ */
+ public void printDictionary(Dictionary<?, ?> dic, String title);
+
+ /**
+ * Prints the given bundle resource if it exists
+ *
+ * @param bundle the bundle containing the resource
+ * @param resource the resource to print
+ */
+ public void printBundleResource(Bundle bundle, String resource);
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/console/CommandProvider.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/console/CommandProvider.java
new file mode 100644
index 000000000..a176cb392
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/console/CommandProvider.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.console;
+
+/**
+ When an object wants to provide a number of commands
+ to the console, it should register an object with this
+ interface. Some console can then pick this up and execute
+ command lines.
+ The SERVICE_RANKING registration property can be used to influence the
+ order that a CommandProvider gets called. Specify a value less than
+ Integer.MAXVALUE, where higher is more significant. The default value
+ if SERVICE_RANKING is not set is 0.
+ <p>
+ The interface contains only methods for the help.
+ The console should use inspection
+ to find the commands. All public commands, starting with
+ a '_' and taking a CommandInterpreter as parameter
+ will be found. E.g.
+ <pre>
+ public Object _hello( CommandInterpreter intp ) {
+ return "hello " + intp.nextArgument();
+ }
+ </pre>
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ * @since 3.1
+ */
+public interface CommandProvider {
+ /**
+ Answer a string (may be as many lines as you like) with help
+ texts that explain the command.
+ */
+ public String getHelp();
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/console/ConsoleSession.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/console/ConsoleSession.java
new file mode 100644
index 000000000..4ad021ca0
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/console/ConsoleSession.java
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.framework.console;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import org.osgi.framework.*;
+
+/**
+ * A console session service provides the input and output to a single console session.
+ * The input will be used by the console to read in console commands. The output will
+ * be used to print the results of console commands.
+ * <p>
+ * The console session must be registered as an OSGi service in order to be associated
+ * with a console instance. The console implementation will discover any console session
+ * services and will create a new console instance using the console session for input and
+ * output. When a session is closed then the console session service will be unregistered
+ * and the console instance will terminate and be disposed of. The console instance will
+ * also terminate if the console session service is unregistered for any reason.
+ * </p>
+ * @since 3.6
+ */
+public abstract class ConsoleSession implements ServiceFactory<Object> {
+ private volatile ServiceRegistration<Object> sessionRegistration;
+
+ /**
+ * Called by the console implementation to free resources associated
+ * with this console session. This method will result in the console
+ * session service being unregistered from the service registry.
+ * @noreference This method is not intended to be referenced by clients.
+ */
+ public final void close() {
+ doClose();
+ ServiceRegistration<Object> current = sessionRegistration;
+ if (current != null) {
+ sessionRegistration = null;
+ try {
+ current.unregister();
+ } catch (IllegalStateException e) {
+ // This can happen if the service is in the process of being
+ // unregistered or if another thread unregistered the service.
+ // Ignoring the exception.
+ }
+ }
+ }
+
+ /**
+ * Called by the {@link #close()} method to free resources associated
+ * with this console session. For example, closing the streams
+ * associated with the input and output for this session.
+ * @noreference This method is not intended to be referenced by clients.
+ */
+ protected abstract void doClose();
+
+ /**
+ * Returns the input for this console session. This input will be used
+ * to read console commands from the user of the session.
+ * @return the input for this console session
+ * @noreference This method is not intended to be referenced by clients.
+ */
+ public abstract InputStream getInput();
+
+ /**
+ * Returns the output for this console session. This output will be
+ * used to write the results of console commands.
+ * @return the output for this console session.
+ * @noreference This method is not intended to be referenced by clients.
+ */
+ public abstract OutputStream getOutput();
+
+ /**
+ * @noreference This method is not intended to be referenced by clients.
+ */
+ public final Object getService(Bundle bundle, ServiceRegistration<Object> registration) {
+ if (sessionRegistration == null)
+ sessionRegistration = registration;
+ return this;
+ }
+
+ /**
+ * @noreference This method is not intended to be referenced by clients.
+ */
+ public final void ungetService(Bundle bundle, ServiceRegistration<Object> registration, Object service) {
+ // do nothing
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/console/package.html b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/console/package.html
new file mode 100644
index 000000000..d140bf5b7
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/console/package.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Package-level Javadoc</title>
+</head>
+<body>
+Provides services related to the Equinox console.
+<h2>
+Package Specification</h2>
+This package specifies the API for services related to the Equinox console.
+<p>
+Clients which provide commands or provide sessions to the Equinox console
+will likely be interested in the types provided by this package.
+</p>
+</body>
+</html>
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/AbstractBundle.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/AbstractBundle.java
new file mode 100644
index 000000000..d2b51fd00
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/AbstractBundle.java
@@ -0,0 +1,1545 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.framework.internal.core;
+
+import java.io.*;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.*;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.*;
+import org.eclipse.osgi.framework.adaptor.*;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.framework.util.KeyedElement;
+import org.eclipse.osgi.internal.loader.BundleLoader;
+import org.eclipse.osgi.internal.permadmin.EquinoxSecurityManager;
+import org.eclipse.osgi.service.resolver.BundleDescription;
+import org.eclipse.osgi.service.resolver.ResolverError;
+import org.eclipse.osgi.signedcontent.*;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.*;
+import org.osgi.framework.hooks.bundle.CollisionHook;
+import org.osgi.framework.startlevel.BundleStartLevel;
+import org.osgi.framework.wiring.*;
+
+/**
+ * This object is given out to bundles and wraps the internal Bundle object. It
+ * is destroyed when a bundle is uninstalled and reused if a bundle is updated.
+ * This class is abstract and is extended by BundleHost and BundleFragment.
+ */
+public abstract class AbstractBundle implements Bundle, Comparable<Bundle>, KeyedElement, BundleStartLevel, BundleReference, BundleRevisions {
+ private final static long STATE_CHANGE_TIMEOUT;
+ static {
+ long stateChangeWait = 5000;
+ try {
+ String prop = FrameworkProperties.getProperty("equinox.statechange.timeout"); //$NON-NLS-1$
+ if (prop != null)
+ stateChangeWait = Long.parseLong(prop);
+ } catch (Throwable t) {
+ // use default 5000
+ stateChangeWait = 5000;
+ }
+ STATE_CHANGE_TIMEOUT = stateChangeWait;
+ }
+ /** The Framework this bundle is part of */
+ protected final Framework framework;
+ /** The state of the bundle. */
+ protected volatile int state;
+ /** A flag to denote whether a bundle state change is in progress */
+ protected volatile Thread stateChanging;
+ /** Bundle's BundleData object */
+ protected BundleData bundledata;
+ /** Internal object used for state change synchronization */
+ protected final Object statechangeLock = new Object();
+ /** ProtectionDomain for the bundle */
+ protected BundleProtectionDomain domain;
+
+ volatile protected ManifestLocalization manifestLocalization = null;
+
+ /**
+ * Bundle object constructor. This constructor should not perform any real
+ * work.
+ *
+ * @param bundledata
+ * BundleData for this bundle
+ * @param framework
+ * Framework this bundle is running in
+ */
+ protected static AbstractBundle createBundle(BundleData bundledata, Framework framework, boolean setBundle) throws BundleException {
+ AbstractBundle result;
+ if ((bundledata.getType() & BundleData.TYPE_FRAGMENT) > 0)
+ result = new BundleFragment(bundledata, framework);
+ else
+ result = new BundleHost(bundledata, framework);
+ if (setBundle)
+ bundledata.setBundle(result);
+ return result;
+ }
+
+ /**
+ * Bundle object constructor. This constructor should not perform any real
+ * work.
+ *
+ * @param bundledata
+ * BundleData for this bundle
+ * @param framework
+ * Framework this bundle is running in
+ */
+ protected AbstractBundle(BundleData bundledata, Framework framework) {
+ state = INSTALLED;
+ stateChanging = null;
+ this.bundledata = bundledata;
+ this.framework = framework;
+ }
+
+ /**
+ * Load the bundle.
+ */
+ protected abstract void load();
+
+ /**
+ * Reload from a new bundle. This method must be called while holding the
+ * bundles lock.
+ *
+ * @param newBundle
+ * Dummy Bundle which contains new data.
+ * @return true if an exported package is "in use". i.e. it has been
+ * imported by a bundle
+ */
+ protected abstract boolean reload(AbstractBundle newBundle);
+
+ /**
+ * Refresh the bundle. This is called by Framework.refreshPackages. This
+ * method must be called while holding the bundles lock.
+ * this.loader.unimportPackages must have already been called before
+ * calling this method!
+ */
+ protected abstract void refresh();
+
+ /**
+ * Unload the bundle. This method must be called while holding the bundles
+ * lock.
+ *
+ * @return true if an exported package is "in use". i.e. it has been
+ * imported by a bundle
+ */
+ protected abstract boolean unload();
+
+ /**
+ * Close the the Bundle's file.
+ *
+ */
+ protected void close() {
+ if (Debug.DEBUG_GENERAL) {
+ if ((state & (INSTALLED)) == 0) {
+ Debug.println("Bundle.close called when state != INSTALLED: " + this); //$NON-NLS-1$
+ Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$
+ }
+ }
+ state = UNINSTALLED;
+ }
+
+ /**
+ * Load and instantiate bundle's BundleActivator class
+ */
+ protected BundleActivator loadBundleActivator() throws BundleException {
+ /* load Bundle's BundleActivator if it has one */
+ String activatorClassName = bundledata.getActivator();
+ if (activatorClassName != null) {
+ try {
+ Class<?> activatorClass = loadClass(activatorClassName, false);
+ /* Create the activator for the bundle */
+ return (BundleActivator) (activatorClass.newInstance());
+ } catch (Throwable t) {
+ if (Debug.DEBUG_GENERAL) {
+ Debug.printStackTrace(t);
+ }
+ throw new BundleException(NLS.bind(Msg.BUNDLE_INVALID_ACTIVATOR_EXCEPTION, activatorClassName, bundledata.getSymbolicName()), BundleException.ACTIVATOR_ERROR, t);
+ }
+ }
+ return (null);
+ }
+
+ /**
+ * This method loads a class from the bundle.
+ *
+ * @param name
+ * the name of the desired Class.
+ * @param checkPermission
+ * indicates whether a permission check should be done.
+ * @return the resulting Class
+ * @exception java.lang.ClassNotFoundException
+ * if the class definition was not found.
+ */
+ protected abstract Class<?> loadClass(String name, boolean checkPermission) throws ClassNotFoundException;
+
+ /**
+ * Returns the current state of the bundle.
+ *
+ * A bundle can only be in one state at any time.
+ *
+ * @return bundle's state.
+ */
+ public int getState() {
+ return (state);
+ }
+
+ public Framework getFramework() {
+ return framework;
+ }
+
+ /**
+ * Return true if the bundle is starting or active.
+ *
+ */
+ protected boolean isActive() {
+ return ((state & (ACTIVE | STARTING)) != 0);
+ }
+
+ boolean isLazyStart() {
+ int status = bundledata.getStatus();
+ return (status & Constants.BUNDLE_ACTIVATION_POLICY) != 0 && (status & Constants.BUNDLE_LAZY_START) != 0;
+ }
+
+ /**
+ * Return true if the bundle is resolved.
+ *
+ */
+ public boolean isResolved() {
+ return (state & (INSTALLED | UNINSTALLED)) == 0;
+ }
+
+ /**
+ * Start this bundle.
+ *
+ * If the current start level is less than this bundle's start level, then
+ * the Framework must persistently mark this bundle as started and delay
+ * the starting of this bundle until the Framework's current start level
+ * becomes equal or more than the bundle's start level.
+ * <p>
+ * Otherwise, the following steps are required to start a bundle:
+ * <ol>
+ * <li>If the bundle is {@link #UNINSTALLED}then an <code>IllegalStateException</code>
+ * is thrown.
+ * <li>If the bundle is {@link #ACTIVE}or {@link #STARTING}then this
+ * method returns immediately.
+ * <li>If the bundle is {@link #STOPPING}then this method may wait for
+ * the bundle to return to the {@link #RESOLVED}state before continuing.
+ * If this does not occur in a reasonable time, a {@link BundleException}
+ * is thrown to indicate the bundle was unable to be started.
+ * <li>If the bundle is not {@link #RESOLVED}, an attempt is made to
+ * resolve the bundle. If the bundle cannot be resolved, a
+ * {@link BundleException}is thrown.
+ * <li>The state of the bundle is set to {@link #STARTING}.
+ * <li>The {@link BundleActivator#start(BundleContext) start}method of the bundle's
+ * {@link BundleActivator}, if one is specified, is called. If the
+ * {@link BundleActivator}is invalid or throws an exception, the state of
+ * the bundle is set back to {@link #RESOLVED}, the bundle's listeners, if
+ * any, are removed, service's registered by the bundle, if any, are
+ * unregistered, and service's used by the bundle, if any, are released. A
+ * {@link BundleException}is then thrown.
+ * <li>It is recorded that this bundle has been started, so that when the
+ * framework is restarted, this bundle will be automatically started.
+ * <li>The state of the bundle is set to {@link #ACTIVE}.
+ * <li>A {@link BundleEvent}of type {@link BundleEvent#STARTED}is
+ * broadcast.
+ * </ol>
+ *
+ * <h5>Preconditons</h5>
+ * <ul>
+ * <li>getState() in {{@link #INSTALLED},{@link #RESOLVED}}.
+ * </ul>
+ * <h5>Postconditons, no exceptions thrown</h5>
+ * <ul>
+ * <li>getState() in {{@link #ACTIVE}}.
+ * <li>{@link BundleActivator#start(BundleContext) BundleActivator.start}has been called
+ * and did not throw an exception.
+ * </ul>
+ * <h5>Postconditions, when an exception is thrown</h5>
+ * <ul>
+ * <li>getState() not in {{@link #STARTING},{@link #ACTIVE}}.
+ * </ul>
+ *
+ * @exception BundleException
+ * If the bundle couldn't be started. This could be because
+ * a code dependency could not be resolved or the specified
+ * BundleActivator could not be loaded or threw an
+ * exception.
+ * @exception java.lang.IllegalStateException
+ * If the bundle has been uninstalled or the bundle tries to
+ * change its own state.
+ * @exception java.lang.SecurityException
+ * If the caller does not have {@link AdminPermission}
+ * permission and the Java runtime environment supports
+ * permissions.
+ */
+ public void start() throws BundleException {
+ start(0);
+ }
+
+ public void start(int options) throws BundleException {
+ framework.checkAdminPermission(this, AdminPermission.EXECUTE);
+ checkValid();
+ beginStateChange();
+ try {
+ startWorker(options);
+ } finally {
+ completeStateChange();
+ }
+ }
+
+ /**
+ * Internal worker to start a bundle.
+ *
+ * @param options
+ */
+ protected abstract void startWorker(int options) throws BundleException;
+
+ /**
+ * This method does the following
+ * <ol>
+ * <li> Return false if the bundle is a fragment
+ * <li> Return false if the bundle is not at the correct start-level
+ * <li> Return false if the bundle is not persistently marked for start
+ * <li> Return true if the bundle's activation policy is persistently ignored
+ * <li> Return true if the bundle does not define an activation policy
+ * <li> Transition to STARTING state and Fire LAZY_ACTIVATION event
+ * <li> Return false
+ * </ol>
+ * @return true if the bundle should be resumed
+ */
+ protected boolean readyToResume() {
+ return false;
+ }
+
+ /**
+ * Start this bundle w/o marking is persistently started.
+ *
+ * <p>
+ * The following steps are followed to start a bundle:
+ * <ol>
+ * <li>If the bundle is {@link #UNINSTALLED}then an <code>IllegalStateException</code>
+ * is thrown.
+ * <li>If the bundle is {@link #ACTIVE}or {@link #STARTING}then this
+ * method returns immediately.
+ * <li>If the bundle is {@link #STOPPING}then this method may wait for
+ * the bundle to return to the {@link #RESOLVED}state before continuing.
+ * If this does not occur in a reasonable time, a {@link BundleException}
+ * is thrown to indicate the bundle was unable to be started.
+ * <li>If the bundle is not {@link #RESOLVED}, an attempt is made to
+ * resolve the bundle. If the bundle cannot be resolved, a
+ * {@link BundleException}is thrown.
+ * <li>The state of the bundle is set to {@link #STARTING}.
+ * <li>The {@link BundleActivator#start(BundleContext) start}method of the bundle's
+ * {@link BundleActivator}, if one is specified, is called. If the
+ * {@link BundleActivator}is invalid or throws an exception, the state of
+ * the bundle is set back to {@link #RESOLVED}, the bundle's listeners, if
+ * any, are removed, service's registered by the bundle, if any, are
+ * unregistered, and service's used by the bundle, if any, are released. A
+ * {@link BundleException}is then thrown.
+ * <li>The state of the bundle is set to {@link #ACTIVE}.
+ * <li>A {@link BundleEvent}of type {@link BundleEvent#STARTED}is
+ * broadcast.
+ * </ol>
+ *
+ * <h5>Preconditons</h5>
+ * <ul>
+ * <li>getState() in {{@link #INSTALLED},{@link #RESOLVED}}.
+ * </ul>
+ * <h5>Postconditons, no exceptions thrown</h5>
+ * <ul>
+ * <li>getState() in {{@link #ACTIVE}}.
+ * <li>{@link BundleActivator#start(BundleContext) BundleActivator.start}has been called
+ * and did not throw an exception.
+ * </ul>
+ * <h5>Postconditions, when an exception is thrown</h5>
+ * <ul>
+ * <li>getState() not in {{@link #STARTING},{@link #ACTIVE}}.
+ * </ul>
+ *
+ * @exception BundleException
+ * If the bundle couldn't be started. This could be because
+ * a code dependency could not be resolved or the specified
+ * BundleActivator could not be loaded or threw an
+ * exception.
+ * @exception java.lang.IllegalStateException
+ * If the bundle tries to change its own state.
+ */
+ protected void resume() throws BundleException {
+ if (state == UNINSTALLED) {
+ return;
+ }
+ beginStateChange();
+ try {
+ if (readyToResume())
+ startWorker(START_TRANSIENT);
+ } finally {
+ completeStateChange();
+ }
+ }
+
+ /**
+ * Stop this bundle.
+ *
+ * Any services registered by this bundle will be unregistered. Any
+ * services used by this bundle will be released. Any listeners registered
+ * by this bundle will be removed.
+ *
+ * <p>
+ * The following steps are followed to stop a bundle:
+ * <ol>
+ * <li>If the bundle is {@link #UNINSTALLED}then an <code>IllegalStateException</code>
+ * is thrown.
+ * <li>If the bundle is {@link #STOPPING},{@link #RESOLVED}, or
+ * {@link #INSTALLED}then this method returns immediately.
+ * <li>If the bundle is {@link #STARTING}then this method may wait for
+ * the bundle to reach the {@link #ACTIVE}state before continuing. If this
+ * does not occur in a reasonable time, a {@link BundleException}is thrown
+ * to indicate the bundle was unable to be stopped.
+ * <li>The state of the bundle is set to {@link #STOPPING}.
+ * <li>It is recorded that this bundle has been stopped, so that when the
+ * framework is restarted, this bundle will not be automatically started.
+ * <li>The {@link BundleActivator#stop(BundleContext) stop}method of the bundle's
+ * {@link BundleActivator}, if one is specified, is called. If the
+ * {@link BundleActivator}throws an exception, this method will continue
+ * to stop the bundle. A {@link BundleException}will be thrown after
+ * completion of the remaining steps.
+ * <li>The bundle's listeners, if any, are removed, service's registered
+ * by the bundle, if any, are unregistered, and service's used by the
+ * bundle, if any, are released.
+ * <li>The state of the bundle is set to {@link #RESOLVED}.
+ * <li>A {@link BundleEvent}of type {@link BundleEvent#STOPPED}is
+ * broadcast.
+ * </ol>
+ *
+ * <h5>Preconditons</h5>
+ * <ul>
+ * <li>getState() in {{@link #ACTIVE}}.
+ * </ul>
+ * <h5>Postconditons, no exceptions thrown</h5>
+ * <ul>
+ * <li>getState() not in {{@link #ACTIVE},{@link #STOPPING}}.
+ * <li>{@link BundleActivator#stop(BundleContext) BundleActivator.stop}has been called
+ * and did not throw an exception.
+ * </ul>
+ * <h5>Postconditions, when an exception is thrown</h5>
+ * <ul>
+ * <li>None.
+ * </ul>
+ *
+ * @exception BundleException
+ * If the bundle's BundleActivator could not be loaded or
+ * threw an exception.
+ * @exception java.lang.IllegalStateException
+ * If the bundle has been uninstalled or the bundle tries to
+ * change its own state.
+ * @exception java.lang.SecurityException
+ * If the caller does not have {@link AdminPermission}
+ * permission and the Java runtime environment supports
+ * permissions.
+ */
+ public void stop() throws BundleException {
+ stop(0);
+ }
+
+ public void stop(int options) throws BundleException {
+ framework.checkAdminPermission(this, AdminPermission.EXECUTE);
+ checkValid();
+ beginStateChange();
+ try {
+ stopWorker(options);
+ } finally {
+ completeStateChange();
+ }
+ }
+
+ /**
+ * Internal worker to stop a bundle.
+ *
+ * @param options
+ */
+ protected abstract void stopWorker(int options) throws BundleException;
+
+ /**
+ * Set the persistent status bit for the bundle.
+ *
+ * @param mask
+ * Mask for bit to set/clear
+ * @param state
+ * true to set bit, false to clear bit
+ */
+ protected void setStatus(final int mask, final boolean state) {
+ try {
+ AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
+ public Object run() throws IOException {
+ int status = bundledata.getStatus();
+ boolean test = ((status & mask) != 0);
+ if (test != state) {
+ bundledata.setStatus(state ? (status | mask) : (status & ~mask));
+ bundledata.save();
+ }
+ return null;
+ }
+ });
+ } catch (PrivilegedActionException pae) {
+ framework.publishFrameworkEvent(FrameworkEvent.ERROR, this, pae.getException());
+ }
+ }
+
+ /**
+ * Stop this bundle w/o marking is persistently stopped.
+ *
+ * Any services registered by this bundle will be unregistered. Any
+ * services used by this bundle will be released. Any listeners registered
+ * by this bundle will be removed.
+ *
+ * <p>
+ * The following steps are followed to stop a bundle:
+ * <ol>
+ * <li>If the bundle is {@link #UNINSTALLED}then an <code>IllegalStateException</code>
+ * is thrown.
+ * <li>If the bundle is {@link #STOPPING},{@link #RESOLVED}, or
+ * {@link #INSTALLED}then this method returns immediately.
+ * <li>If the bundle is {@link #STARTING}then this method may wait for
+ * the bundle to reach the {@link #ACTIVE}state before continuing. If this
+ * does not occur in a reasonable time, a {@link BundleException}is thrown
+ * to indicate the bundle was unable to be stopped.
+ * <li>The state of the bundle is set to {@link #STOPPING}.
+ * <li>The {@link BundleActivator#stop(BundleContext) stop}method of the bundle's
+ * {@link BundleActivator}, if one is specified, is called. If the
+ * {@link BundleActivator}throws an exception, this method will continue
+ * to stop the bundle. A {@link BundleException}will be thrown after
+ * completion of the remaining steps.
+ * <li>The bundle's listeners, if any, are removed, service's registered
+ * by the bundle, if any, are unregistered, and service's used by the
+ * bundle, if any, are released.
+ * <li>The state of the bundle is set to {@link #RESOLVED}.
+ * <li>A {@link BundleEvent}of type {@link BundleEvent#STOPPED}is
+ * broadcast.
+ * </ol>
+ *
+ * <h5>Preconditons</h5>
+ * <ul>
+ * <li>getState() in {{@link #ACTIVE}}.
+ * </ul>
+ * <h5>Postconditons, no exceptions thrown</h5>
+ * <ul>
+ * <li>getState() not in {{@link #ACTIVE},{@link #STOPPING}}.
+ * <li>{@link BundleActivator#stop(BundleContext) BundleActivator.stop}has been called
+ * and did not throw an exception.
+ * </ul>
+ * <h5>Postconditions, when an exception is thrown</h5>
+ * <ul>
+ * <li>None.
+ * </ul>
+ *
+ * @param lock
+ * true if state change lock should be held when returning from
+ * this method.
+ * @exception BundleException
+ * If the bundle's BundleActivator could not be loaded or
+ * threw an exception.
+ * @exception java.lang.IllegalStateException
+ * If the bundle tries to change its own state.
+ */
+ protected void suspend(boolean lock) throws BundleException {
+ if (state == UNINSTALLED) {
+ return;
+ }
+ beginStateChange();
+ try {
+ stopWorker(STOP_TRANSIENT);
+ } finally {
+ if (!lock) {
+ completeStateChange();
+ }
+ }
+ }
+
+ public void update() throws BundleException {
+ update(null);
+ }
+
+ public void update(final InputStream in) throws BundleException {
+ if (Debug.DEBUG_GENERAL) {
+ Debug.println("update location " + bundledata.getLocation()); //$NON-NLS-1$
+ Debug.println(" from: " + in); //$NON-NLS-1$
+ }
+ framework.checkAdminPermission(this, AdminPermission.LIFECYCLE);
+ if ((bundledata.getType() & (BundleData.TYPE_BOOTCLASSPATH_EXTENSION | BundleData.TYPE_FRAMEWORK_EXTENSION | BundleData.TYPE_EXTCLASSPATH_EXTENSION)) != 0)
+ // need special permission to update extensions
+ framework.checkAdminPermission(this, AdminPermission.EXTENSIONLIFECYCLE);
+ checkValid();
+ beginStateChange();
+ try {
+ final AccessControlContext callerContext = AccessController.getContext();
+ //note AdminPermission is checked again after updated bundle is loaded
+ updateWorker(new PrivilegedExceptionAction<Object>() {
+ public Object run() throws BundleException {
+ /* compute the update location */
+ URLConnection source = null;
+ if (in == null) {
+ String updateLocation = bundledata.getManifest().get(Constants.BUNDLE_UPDATELOCATION);
+ if (updateLocation == null)
+ updateLocation = bundledata.getLocation();
+ if (Debug.DEBUG_GENERAL)
+ Debug.println(" from location: " + updateLocation); //$NON-NLS-1$
+ /* Map the update location to a URLConnection */
+ source = framework.adaptor.mapLocationToURLConnection(updateLocation);
+ } else {
+ /* Map the InputStream to a URLConnection */
+ source = new BundleSource(in);
+ }
+ /* call the worker */
+ updateWorkerPrivileged(source, callerContext);
+ return null;
+ }
+ });
+ } finally {
+ completeStateChange();
+ }
+ }
+
+ /**
+ * Update worker. Assumes the caller has the state change lock.
+ */
+ protected void updateWorker(PrivilegedExceptionAction<Object> action) throws BundleException {
+ int previousState = 0;
+ if (!isFragment())
+ previousState = state;
+ if ((previousState & (ACTIVE | STARTING)) != 0) {
+ try {
+ stopWorker(STOP_TRANSIENT);
+ } catch (BundleException e) {
+ framework.publishFrameworkEvent(FrameworkEvent.ERROR, this, e);
+ if ((state & (ACTIVE | STARTING)) != 0) /* if the bundle is still active */{
+ throw e;
+ }
+ }
+ }
+ try {
+ AccessController.doPrivileged(action);
+ framework.publishBundleEvent(BundleEvent.UPDATED, this);
+ } catch (PrivilegedActionException pae) {
+ if (pae.getException() instanceof RuntimeException)
+ throw (RuntimeException) pae.getException();
+ throw (BundleException) pae.getException();
+ } finally {
+ if ((previousState & (ACTIVE | STARTING)) != 0) {
+ try {
+ startWorker(START_TRANSIENT | ((previousState & STARTING) != 0 ? START_ACTIVATION_POLICY : 0));
+ } catch (BundleException e) {
+ framework.publishFrameworkEvent(FrameworkEvent.ERROR, this, e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Update worker. Assumes the caller has the state change lock.
+ */
+ protected void updateWorkerPrivileged(URLConnection source, AccessControlContext callerContext) throws BundleException {
+ AbstractBundle oldBundle = AbstractBundle.createBundle(bundledata, framework, false);
+ boolean reloaded = false;
+ BundleOperation storage = framework.adaptor.updateBundle(this.bundledata, source);
+ BundleRepository bundles = framework.getBundles();
+ try {
+ BundleData newBundleData = storage.begin();
+ // Must call framework createBundle to check execution environment.
+ final AbstractBundle newBundle = framework.createAndVerifyBundle(CollisionHook.UPDATING, this, newBundleData, false);
+ boolean exporting;
+ int st = getState();
+ synchronized (bundles) {
+ String oldBSN = this.getSymbolicName();
+ exporting = reload(newBundle);
+ // update this to flush the old BSN/version etc.
+ bundles.update(oldBSN, this);
+ manifestLocalization = null;
+ }
+ // indicate we have loaded from the new version of the bundle
+ reloaded = true;
+ if (System.getSecurityManager() != null) {
+ final boolean extension = (bundledata.getType() & (BundleData.TYPE_BOOTCLASSPATH_EXTENSION | BundleData.TYPE_FRAMEWORK_EXTENSION | BundleData.TYPE_EXTCLASSPATH_EXTENSION)) != 0;
+ // must check for AllPermission before allow a bundle extension to be updated
+ if (extension && !hasPermission(new AllPermission()))
+ throw new BundleException(Msg.BUNDLE_EXTENSION_PERMISSION, BundleException.SECURITY_ERROR, new SecurityException(Msg.BUNDLE_EXTENSION_PERMISSION));
+ try {
+ AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
+ public Object run() throws Exception {
+ framework.checkAdminPermission(newBundle, AdminPermission.LIFECYCLE);
+ if (extension) // need special permission to update extension bundles
+ framework.checkAdminPermission(newBundle, AdminPermission.EXTENSIONLIFECYCLE);
+ return null;
+ }
+ }, callerContext);
+ } catch (PrivilegedActionException e) {
+ throw e.getException();
+ }
+ }
+ // send out unresolved events outside synch block (defect #80610)
+ if (st == RESOLVED)
+ framework.publishBundleEvent(BundleEvent.UNRESOLVED, this);
+ storage.commit(exporting);
+ } catch (Throwable t) {
+ try {
+ storage.undo();
+ if (reloaded)
+ /*
+ * if we loaded from the new version of the
+ * bundle
+ */{
+ synchronized (bundles) {
+ String oldBSN = this.getSymbolicName();
+ reload(oldBundle);
+ // update this to flush the new BSN/version back to the old one etc.
+ bundles.update(oldBSN, this);
+ }
+ }
+ } catch (BundleException ee) {
+ /* if we fail to revert then we are in big trouble */
+ framework.publishFrameworkEvent(FrameworkEvent.ERROR, this, ee);
+ }
+ if (t instanceof SecurityException)
+ throw (SecurityException) t;
+ if (t instanceof BundleException)
+ throw (BundleException) t;
+ throw new BundleException(t.getMessage(), t);
+ }
+ }
+
+ /**
+ * Uninstall this bundle.
+ * <p>
+ * This method removes all traces of the bundle, including any data in the
+ * persistent storage area provided for the bundle by the framework.
+ *
+ * <p>
+ * The following steps are followed to uninstall a bundle:
+ * <ol>
+ * <li>If the bundle is {@link #UNINSTALLED}then an <code>IllegalStateException</code>
+ * is thrown.
+ * <li>If the bundle is {@link #ACTIVE}or {@link #STARTING}, the bundle
+ * is stopped as described in the {@link #stop()}method. If {@link #stop()}
+ * throws an exception, a {@link FrameworkEvent}of type
+ * {@link FrameworkEvent#ERROR}is broadcast containing the exception.
+ * <li>A {@link BundleEvent}of type {@link BundleEvent#UNINSTALLED}is
+ * broadcast.
+ * <li>The state of the bundle is set to {@link #UNINSTALLED}.
+ * <li>The bundle and the persistent storage area provided for the bundle
+ * by the framework, if any, is removed.
+ * </ol>
+ *
+ * <h5>Preconditions</h5>
+ * <ul>
+ * <li>getState() not in {{@link #UNINSTALLED}}.
+ * </ul>
+ * <h5>Postconditons, no exceptions thrown</h5>
+ * <ul>
+ * <li>getState() in {{@link #UNINSTALLED}}.
+ * <li>The bundle has been uninstalled.
+ * </ul>
+ * <h5>Postconditions, when an exception is thrown</h5>
+ * <ul>
+ * <li>getState() not in {{@link #UNINSTALLED}}.
+ * <li>The Bundle has not been uninstalled.
+ * </ul>
+ *
+ * @exception BundleException
+ * If the uninstall failed.
+ * @exception java.lang.IllegalStateException
+ * If the bundle has been uninstalled or the bundle tries to
+ * change its own state.
+ * @exception java.lang.SecurityException
+ * If the caller does not have {@link AdminPermission}
+ * permission and the Java runtime environment supports
+ * permissions.
+ * @see #stop()
+ */
+ public void uninstall() throws BundleException {
+ if (Debug.DEBUG_GENERAL) {
+ Debug.println("uninstall location: " + bundledata.getLocation()); //$NON-NLS-1$
+ }
+ framework.checkAdminPermission(this, AdminPermission.LIFECYCLE);
+ if ((bundledata.getType() & (BundleData.TYPE_BOOTCLASSPATH_EXTENSION | BundleData.TYPE_FRAMEWORK_EXTENSION | BundleData.TYPE_EXTCLASSPATH_EXTENSION)) != 0)
+ // need special permission to uninstall extensions
+ framework.checkAdminPermission(this, AdminPermission.EXTENSIONLIFECYCLE);
+ checkValid();
+ beginStateChange();
+ try {
+ uninstallWorker(new PrivilegedExceptionAction<Object>() {
+ public Object run() throws BundleException {
+ uninstallWorkerPrivileged();
+ return null;
+ }
+ });
+ } finally {
+ completeStateChange();
+ }
+ }
+
+ /**
+ * Uninstall worker. Assumes the caller has the state change lock.
+ */
+ protected void uninstallWorker(PrivilegedExceptionAction<Object> action) throws BundleException {
+ boolean bundleActive = false;
+ if (!isFragment())
+ bundleActive = (state & (ACTIVE | STARTING)) != 0;
+ if (bundleActive) {
+ try {
+ stopWorker(STOP_TRANSIENT);
+ } catch (BundleException e) {
+ framework.publishFrameworkEvent(FrameworkEvent.ERROR, this, e);
+ }
+ }
+ try {
+ AccessController.doPrivileged(action);
+ } catch (PrivilegedActionException pae) {
+ if (bundleActive) /* if we stopped the bundle */{
+ try {
+ startWorker(START_TRANSIENT);
+ } catch (BundleException e) {
+ /*
+ * if we fail to start the original bundle then we are in
+ * big trouble
+ */
+ framework.publishFrameworkEvent(FrameworkEvent.ERROR, this, e);
+ }
+ }
+ throw (BundleException) pae.getException();
+ }
+ framework.publishBundleEvent(BundleEvent.UNINSTALLED, this);
+ }
+
+ /**
+ * Uninstall worker. Assumes the caller has the state change lock.
+ */
+ protected void uninstallWorkerPrivileged() throws BundleException {
+ BundleWatcher bundleStats = framework.adaptor.getBundleWatcher();
+ if (bundleStats != null)
+ bundleStats.watchBundle(this, BundleWatcher.START_UNINSTALLING);
+ boolean unloaded = false;
+ //cache the bundle's headers
+ getHeaders();
+ BundleOperation storage = framework.adaptor.uninstallBundle(this.bundledata);
+ BundleRepository bundles = framework.getBundles();
+ try {
+ storage.begin();
+ boolean exporting;
+ int st = getState();
+ synchronized (bundles) {
+ bundles.remove(this); /* remove before calling unload */
+ exporting = unload();
+ }
+ // send out unresolved events outside synch block (defect #80610)
+ if (st == RESOLVED)
+ framework.publishBundleEvent(BundleEvent.UNRESOLVED, this);
+ unloaded = true;
+ storage.commit(exporting);
+ close();
+ } catch (BundleException e) {
+ try {
+ storage.undo();
+ if (unloaded) /* if we unloaded the bundle */{
+ synchronized (bundles) {
+ load(); /* reload the bundle */
+ bundles.add(this);
+ }
+ }
+ } catch (BundleException ee) {
+ /*
+ * if we fail to load the original bundle then we are in big
+ * trouble
+ */
+ framework.publishFrameworkEvent(FrameworkEvent.ERROR, this, ee);
+ }
+ throw e;
+ } finally {
+ if (bundleStats != null)
+ bundleStats.watchBundle(this, BundleWatcher.END_UNINSTALLING);
+ }
+ }
+
+ /**
+ * Return the bundle's manifest headers and values from the manifest's
+ * preliminary section. That is all the manifest's headers and values prior
+ * to the first blank line.
+ *
+ * <p>
+ * Manifest header names are case-insensitive. The methods of the returned
+ * <code>Dictionary</code> object will operate on header names in a
+ * case-insensitive manner.
+ *
+ * <p>
+ * For example, the following manifest headers and values are included if
+ * they are present in the manifest:
+ *
+ * <pre>
+ * Bundle-Name
+ * Bundle-Vendor
+ * Bundle-Version
+ * Bundle-Description
+ * Bundle-DocURL
+ * Bundle-ContactAddress
+ * </pre>
+ *
+ * <p>
+ * This method will continue to return this information when the bundle is
+ * in the {@link #UNINSTALLED}state.
+ *
+ * @return A <code>Dictionary</code> object containing the bundle's
+ * manifest headers and values.
+ * @exception java.lang.SecurityException
+ * If the caller does not have {@link AdminPermission}
+ * permission and the Java runtime environment supports
+ * permissions.
+ */
+ public Dictionary<String, String> getHeaders() {
+ return getHeaders(null);
+ }
+
+ /**
+ * Returns this bundle's Manifest headers and values. This method returns
+ * all the Manifest headers and values from the main section of the
+ * bundle's Manifest file; that is, all lines prior to the first blank
+ * line.
+ *
+ * <p>
+ * Manifest header names are case-insensitive. The methods of the returned
+ * <tt>Dictionary</tt> object will operate on header names in a
+ * case-insensitive manner.
+ *
+ * If a Manifest header begins with a '%', it will be evaluated with the
+ * specified properties file for the specied Locale.
+ *
+ * <p>
+ * For example, the following Manifest headers and values are included if
+ * they are present in the Manifest file:
+ *
+ * <pre>
+ * Bundle-Name
+ * Bundle-Vendor
+ * Bundle-Version
+ * Bundle-Description
+ * Bundle-DocURL
+ * Bundle-ContactAddress
+ * </pre>
+ *
+ * <p>
+ * This method will continue to return Manifest header information while
+ * this bundle is in the <tt>UNINSTALLED</tt> state.
+ *
+ * @return A <tt>Dictionary</tt> object containing this bundle's Manifest
+ * headers and values.
+ *
+ * @exception java.lang.SecurityException
+ * If the caller does not have the <tt>AdminPermission</tt>,
+ * and the Java Runtime Environment supports permissions.
+ */
+ public Dictionary<String, String> getHeaders(String localeString) {
+ framework.checkAdminPermission(this, AdminPermission.METADATA);
+ ManifestLocalization localization;
+ try {
+ localization = getManifestLocalization();
+ } catch (BundleException e) {
+ framework.publishFrameworkEvent(FrameworkEvent.ERROR, this, e);
+ // return an empty dictinary.
+ return new Hashtable<String, String>();
+ }
+ if (localeString == null)
+ localeString = Locale.getDefault().toString();
+ return localization.getHeaders(localeString);
+ }
+
+ /**
+ * Retrieve the bundle's unique identifier, which the framework assigned to
+ * this bundle when it was installed.
+ *
+ * <p>
+ * The unique identifier has the following attributes:
+ * <ul>
+ * <li>It is unique and persistent.
+ * <li>The identifier is a long.
+ * <li>Once its value is assigned to a bundle, that value is not reused
+ * for another bundle, even after the bundle is uninstalled.
+ * <li>Its value does not change as long as the bundle remains installed.
+ * <li>Its value does not change when the bundle is updated
+ * </ul>
+ *
+ * <p>
+ * This method will continue to return the bundle's unique identifier when
+ * the bundle is in the {@link #UNINSTALLED}state.
+ *
+ * @return This bundle's unique identifier.
+ */
+ public long getBundleId() {
+ return (bundledata.getBundleID());
+ }
+
+ /**
+ * Retrieve the location identifier of the bundle. This is typically the
+ * location passed to
+ * {@link BundleContextImpl#installBundle(String) BundleContext.installBundle}when the
+ * bundle was installed. The location identifier of the bundle may change
+ * during bundle update. Calling this method while framework is updating
+ * the bundle results in undefined behavior.
+ *
+ * <p>
+ * This method will continue to return the bundle's location identifier
+ * when the bundle is in the {@link #UNINSTALLED}state.
+ *
+ * @return A string that is the location identifier of the bundle.
+ * @exception java.lang.SecurityException
+ * If the caller does not have {@link AdminPermission}
+ * permission and the Java runtime environment supports
+ * permissions.
+ */
+ public String getLocation() {
+ framework.checkAdminPermission(this, AdminPermission.METADATA);
+ return (bundledata.getLocation());
+ }
+
+ /**
+ * Determine whether the bundle has the requested permission.
+ *
+ * <p>
+ * If the Java runtime environment does not supports permissions this
+ * method always returns <code>true</code>. The permission parameter is
+ * of type <code>Object</code> to avoid referencing the <code>java.security.Permission</code>
+ * class directly. This is to allow the framework to be implemented in Java
+ * environments which do not support permissions.
+ *
+ * @param permission
+ * The requested permission.
+ * @return <code>true</code> if the bundle has the requested permission
+ * or <code>false</code> if the bundle does not have the
+ * permission or the permission parameter is not an <code>instanceof java.security.Permission</code>.
+ * @exception java.lang.IllegalStateException
+ * If the bundle has been uninstalled.
+ */
+ public boolean hasPermission(Object permission) {
+ checkValid();
+ if (domain != null) {
+ if (permission instanceof Permission) {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm instanceof EquinoxSecurityManager) {
+ /*
+ * If the FrameworkSecurityManager is active, we need to do checks the "right" way.
+ * We can exploit our knowledge that the security context of FrameworkSecurityManager
+ * is an AccessControlContext to invoke it properly with the ProtectionDomain.
+ */
+ AccessControlContext acc = getAccessControlContext();
+ try {
+ sm.checkPermission((Permission) permission, acc);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+ return domain.implies((Permission) permission);
+ }
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * This method marks the bundle's state as changing so that other calls to
+ * start/stop/suspend/update/uninstall can wait until the state change is
+ * complete. If stateChanging is non-null when this method is called, we
+ * will wait for the state change to complete. If the timeout expires
+ * without changing state (this may happen if the state change is back up
+ * our call stack), a BundleException is thrown so that we don't wait
+ * forever.
+ *
+ * A call to this method should be immediately followed by a try block
+ * whose finally block calls completeStateChange().
+ *
+ * beginStateChange(); try { // change the bundle's state here... } finally {
+ * completeStateChange(); }
+ *
+ * @exception org.osgi.framework.BundleException
+ * if the bundles state is still changing after waiting for
+ * the timeout.
+ */
+ protected void beginStateChange() throws BundleException {
+ synchronized (statechangeLock) {
+ boolean doubleFault = false;
+ while (true) {
+ if (stateChanging == null) {
+ stateChanging = Thread.currentThread();
+ return;
+ }
+ if (doubleFault || (stateChanging == Thread.currentThread())) {
+ throw new BundleException(NLS.bind(Msg.BUNDLE_STATE_CHANGE_EXCEPTION, getBundleData().getLocation(), stateChanging.getName()), BundleException.STATECHANGE_ERROR, new BundleStatusException(null, StatusException.CODE_WARNING, stateChanging));
+ }
+ try {
+ long start = 0;
+ if (Debug.DEBUG_GENERAL) {
+ Debug.println(" Waiting for state to change in bundle " + this); //$NON-NLS-1$
+ start = System.currentTimeMillis();
+ }
+ statechangeLock.wait(STATE_CHANGE_TIMEOUT);
+ /*
+ * wait for other thread to
+ * finish changing state
+ */
+ if (Debug.DEBUG_GENERAL) {
+ long end = System.currentTimeMillis();
+ if (end - start > 0)
+ System.out.println("Waiting... : " + getSymbolicName() + ' ' + (end - start)); //$NON-NLS-1$
+ }
+ } catch (InterruptedException e) {
+ //Nothing to do
+ }
+ doubleFault = true;
+ }
+ }
+ }
+
+ /**
+ * This method completes the bundle state change by setting stateChanging
+ * to null and notifying one waiter that the state change has completed.
+ */
+ protected void completeStateChange() {
+ synchronized (statechangeLock) {
+ if (stateChanging == Thread.currentThread()) {
+ stateChanging = null;
+ statechangeLock.notify();
+ /*
+ * notify one waiting thread that the
+ * state change is complete
+ */
+ }
+ }
+ }
+
+ /**
+ * Return a string representation of this bundle.
+ *
+ * @return String
+ */
+ public String toString() {
+ String name = bundledata.getSymbolicName();
+ if (name == null)
+ name = "unknown"; //$NON-NLS-1$
+ return (name + '_' + bundledata.getVersion() + " [" + getBundleId() + "]"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ /**
+ * Answers an integer indicating the relative positions of the receiver and
+ * the argument in the natural order of elements of the receiver's class.
+ *
+ * @return int which should be <0 if the receiver should sort before the
+ * argument, 0 if the receiver should sort in the same position as
+ * the argument, and >0 if the receiver should sort after the
+ * argument.
+ * @param obj
+ * another Bundle an object to compare the receiver to
+ * @exception ClassCastException
+ * if the argument can not be converted into something
+ * comparable with the receiver.
+ */
+ public int compareTo(Bundle obj) {
+ int slcomp = getInternalStartLevel() - ((AbstractBundle) obj).getInternalStartLevel();
+ if (slcomp != 0) {
+ return slcomp;
+ }
+ long idcomp = getBundleId() - ((AbstractBundle) obj).getBundleId();
+ return (idcomp < 0L) ? -1 : ((idcomp > 0L) ? 1 : 0);
+ }
+
+ /**
+ * This method checks that the bundle is not uninstalled. If the bundle is
+ * uninstalled, an IllegalStateException is thrown.
+ *
+ * @exception java.lang.IllegalStateException
+ * If the bundle is uninstalled.
+ */
+ protected void checkValid() {
+ if (state == UNINSTALLED) {
+ throw new IllegalStateException(NLS.bind(Msg.BUNDLE_UNINSTALLED_EXCEPTION, getBundleData().getLocation()));
+ }
+ }
+
+ /**
+ * Get the bundle's ProtectionDomain.
+ *
+ * @return bundle's ProtectionDomain.
+ */
+ public BundleProtectionDomain getProtectionDomain() {
+ return domain;
+ }
+
+ private AccessControlContext getAccessControlContext() {
+ return new AccessControlContext(new ProtectionDomain[] {domain});
+ }
+
+ protected BundleFragment[] getFragments() {
+ checkValid();
+ return null;
+ }
+
+ protected boolean isFragment() {
+ return false;
+ }
+
+ BundleHost[] getHosts() {
+ checkValid();
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.osgi.framework.Bundle#findClass(java.lang.String)
+ */
+ public Class<?> loadClass(String classname) throws ClassNotFoundException {
+ return loadClass(classname, true);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.osgi.framework.Bundle#getResourcePaths(java.lang.String)
+ */
+ public Enumeration<String> getEntryPaths(final String path) {
+ try {
+ framework.checkAdminPermission(this, AdminPermission.RESOURCE);
+ } catch (SecurityException e) {
+ return null;
+ }
+ checkValid();
+ // TODO this doPrivileged is probably not needed. The adaptor isolates callers from disk access
+ return AccessController.doPrivileged(new PrivilegedAction<Enumeration<String>>() {
+ public Enumeration<String> run() {
+ return bundledata.getEntryPaths(path);
+ }
+ });
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.osgi.framework.Bundle#getFile(java.lang.String)
+ */
+ public URL getEntry(String fileName) {
+ try {
+ framework.checkAdminPermission(this, AdminPermission.RESOURCE);
+ } catch (SecurityException e) {
+ return null;
+ }
+ return getEntry0(fileName);
+ }
+
+ URL getEntry0(String fileName) {
+ checkValid();
+ return bundledata.getEntry(fileName);
+ }
+
+ public String getSymbolicName() {
+ return bundledata.getSymbolicName();
+ }
+
+ public long getLastModified() {
+ return bundledata.getLastModified();
+ }
+
+ public BundleData getBundleData() {
+ return bundledata;
+ }
+
+ public Version getVersion() {
+ return bundledata.getVersion();
+ }
+
+ public BundleDescription getBundleDescription() {
+ return framework.adaptor.getState().getBundle(getBundleId());
+ }
+
+ int getInternalStartLevel() {
+ return bundledata.getStartLevel();
+ }
+
+ protected abstract BundleLoader getBundleLoader();
+
+ /**
+ * Mark this bundle as resolved.
+ */
+ protected void resolve() {
+ if (Debug.DEBUG_GENERAL) {
+ if ((state & (INSTALLED)) == 0) {
+ Debug.println("Bundle.resolve called when state != INSTALLED: " + this); //$NON-NLS-1$
+ Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$
+ }
+ }
+ if (state == INSTALLED) {
+ state = RESOLVED;
+ // Do not publish RESOLVED event here. This is done by caller
+ // to resolve if appropriate.
+ }
+ }
+
+ public BundleContext getBundleContext() {
+ framework.checkAdminPermission(this, AdminPermission.CONTEXT);
+ return getContext();
+ }
+
+ /**
+ * Return the current context for this bundle.
+ *
+ * @return BundleContext for this bundle.
+ */
+ abstract protected BundleContextImpl getContext();
+
+ public BundleException getResolutionFailureException() {
+ BundleDescription bundleDescription = getBundleDescription();
+ if (bundleDescription == null)
+ return new BundleException(NLS.bind(Msg.BUNDLE_UNRESOLVED_EXCEPTION, this.toString()), BundleException.RESOLVE_ERROR);
+ // just a sanity check - this would be an inconsistency between the framework and the state
+ if (bundleDescription.isResolved())
+ return new BundleException(Msg.BUNDLE_UNRESOLVED_STATE_CONFLICT, BundleException.RESOLVE_ERROR);
+ return getResolverError(bundleDescription);
+ }
+
+ private BundleException getResolverError(BundleDescription bundleDesc) {
+ ResolverError[] errors = framework.adaptor.getState().getResolverErrors(bundleDesc);
+ if (errors == null || errors.length == 0)
+ return new BundleException(NLS.bind(Msg.BUNDLE_UNRESOLVED_EXCEPTION, this.toString()), BundleException.RESOLVE_ERROR);
+ StringBuffer message = new StringBuffer();
+ int errorType = BundleException.RESOLVE_ERROR;
+ for (int i = 0; i < errors.length; i++) {
+ if ((errors[i].getType() & ResolverError.INVALID_NATIVECODE_PATHS) != 0)
+ errorType = BundleException.NATIVECODE_ERROR;
+ message.append(errors[i].toString());
+ if (i < errors.length - 1)
+ message.append(", "); //$NON-NLS-1$
+ }
+ return new BundleException(NLS.bind(Msg.BUNDLE_UNRESOLVED_UNSATISFIED_CONSTRAINT_EXCEPTION, this.toString(), message.toString()), errorType);
+ }
+
+ public int getKeyHashCode() {
+ long id = getBundleId();
+ return (int) (id ^ (id >>> 32));
+ }
+
+ public boolean compare(KeyedElement other) {
+ return getBundleId() == ((AbstractBundle) other).getBundleId();
+ }
+
+ public Object getKey() {
+ return new Long(getBundleId());
+ }
+
+ /* This method is used by the Bundle Localization Service to obtain
+ * a ResourceBundle that resides in a bundle. This is not an OSGi
+ * defined method for org.osgi.framework.Bundle
+ *
+ */
+ public ResourceBundle getResourceBundle(String localeString) {
+ ManifestLocalization localization;
+ try {
+ localization = getManifestLocalization();
+ } catch (BundleException ex) {
+ return (null);
+ }
+ String defaultLocale = Locale.getDefault().toString();
+ if (localeString == null) {
+ localeString = defaultLocale;
+ }
+ return localization.getResourceBundle(localeString, defaultLocale.equals(localeString));
+ }
+
+ private synchronized ManifestLocalization getManifestLocalization() throws BundleException {
+ ManifestLocalization currentLocalization = manifestLocalization;
+ if (currentLocalization == null) {
+ Dictionary<String, String> rawHeaders = bundledata.getManifest();
+ manifestLocalization = currentLocalization = new ManifestLocalization(this, rawHeaders);
+ }
+ return currentLocalization;
+ }
+
+ public boolean testStateChanging(Object thread) {
+ return stateChanging == thread;
+ }
+
+ public Thread getStateChanging() {
+ return stateChanging;
+ }
+
+ public Enumeration<URL> findEntries(String path, String filePattern, boolean recurse) {
+ try {
+ framework.checkAdminPermission(this, AdminPermission.RESOURCE);
+ } catch (SecurityException e) {
+ return null;
+ }
+ checkValid();
+ // check to see if the bundle is resolved
+ if (!isResolved())
+ framework.packageAdmin.resolveBundles(new Bundle[] {this});
+
+ // if this bundle is a host to fragments then search the fragments
+ BundleFragment[] fragments = getFragments();
+ List<BundleData> datas = new ArrayList<BundleData>((fragments == null ? 0 : fragments.length) + 1);
+ datas.add(getBundleData());
+ if (fragments != null)
+ for (BundleFragment fragment : fragments)
+ datas.add(fragment.getBundleData());
+ int options = recurse ? BundleWiring.FINDENTRIES_RECURSE : 0;
+ return framework.getAdaptor().findEntries(datas, path, filePattern, options);
+ }
+
+ class BundleStatusException extends Throwable implements StatusException {
+ private static final long serialVersionUID = 7201911791818929100L;
+ private int code;
+ private transient Object status;
+
+ BundleStatusException(String message, int code, Object status) {
+ super(message);
+ this.code = code;
+ this.status = status;
+ }
+
+ public Object getStatus() {
+ return status;
+ }
+
+ public int getStatusCode() {
+ return code;
+ }
+
+ }
+
+ public Map<X509Certificate, List<X509Certificate>> getSignerCertificates(int signersType) {
+ @SuppressWarnings("unchecked")
+ final Map<X509Certificate, List<X509Certificate>> empty = Collections.EMPTY_MAP;
+ if (signersType != SIGNERS_ALL && signersType != SIGNERS_TRUSTED)
+ throw new IllegalArgumentException("Invalid signers type: " + signersType); //$NON-NLS-1$
+ if (framework == null)
+ return empty;
+ SignedContentFactory factory = framework.getSignedContentFactory();
+ if (factory == null)
+ return empty;
+ try {
+ SignedContent signedContent = factory.getSignedContent(this);
+ SignerInfo[] infos = signedContent.getSignerInfos();
+ if (infos.length == 0)
+ return empty;
+ Map<X509Certificate, List<X509Certificate>> results = new HashMap<X509Certificate, List<X509Certificate>>(infos.length);
+ for (int i = 0; i < infos.length; i++) {
+ if (signersType == SIGNERS_TRUSTED && !infos[i].isTrusted())
+ continue;
+ Certificate[] certs = infos[i].getCertificateChain();
+ if (certs == null || certs.length == 0)
+ continue;
+ List<X509Certificate> certChain = new ArrayList<X509Certificate>();
+ for (int j = 0; j < certs.length; j++)
+ certChain.add((X509Certificate) certs[j]);
+ results.put((X509Certificate) certs[0], certChain);
+ }
+ return results;
+ } catch (Exception e) {
+ return empty;
+ }
+ }
+
+ public final <A> A adapt(Class<A> adapterType) {
+ checkAdaptPermission(adapterType);
+ return adapt0(adapterType);
+ }
+
+ public List<BundleRevision> getRevisions() {
+ List<BundleRevision> revisions = new ArrayList<BundleRevision>();
+ BundleDescription current = getBundleDescription();
+ if (current != null)
+ revisions.add(current);
+ BundleDescription[] removals = framework.adaptor.getState().getRemovalPending();
+ for (BundleDescription removed : removals) {
+ if (removed.getBundleId() == getBundleId() && removed != current) {
+ revisions.add(removed);
+ }
+ }
+ return revisions;
+ }
+
+ @SuppressWarnings("unchecked")
+ protected <A> A adapt0(Class<A> adapterType) {
+ if (adapterType.isInstance(this))
+ return (A) this;
+ if (BundleContext.class.equals(adapterType)) {
+ try {
+ return (A) getBundleContext();
+ } catch (SecurityException e) {
+ return null;
+ }
+ }
+
+ if (AccessControlContext.class.equals(adapterType)) {
+ return (A) getAccessControlContext();
+ }
+
+ if (BundleWiring.class.equals(adapterType)) {
+ if (state == UNINSTALLED)
+ return null;
+ BundleDescription description = getBundleDescription();
+ return (A) description.getWiring();
+ }
+
+ if (BundleRevision.class.equals(adapterType)) {
+ if (state == UNINSTALLED)
+ return null;
+ return (A) getBundleDescription();
+ }
+ return null;
+ }
+
+ /**
+ * Check for permission to get a service.
+ */
+ private <A> void checkAdaptPermission(Class<A> adapterType) {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm == null) {
+ return;
+ }
+ sm.checkPermission(new AdaptPermission(adapterType.getName(), this, AdaptPermission.ADAPT));
+ }
+
+ public File getDataFile(String filename) {
+ return framework.getDataFile(this, filename);
+ }
+
+ public Bundle getBundle() {
+ return this;
+ }
+
+ public int getStartLevel() {
+ if (getState() == Bundle.UNINSTALLED)
+ throw new IllegalArgumentException(NLS.bind(Msg.BUNDLE_UNINSTALLED_EXCEPTION, getBundleData().getLocation()));
+ return getInternalStartLevel();
+ }
+
+ public void setStartLevel(int startlevel) {
+ framework.startLevelManager.setBundleStartLevel(this, startlevel);
+ }
+
+ public boolean isPersistentlyStarted() {
+ if (getState() == Bundle.UNINSTALLED)
+ throw new IllegalArgumentException(NLS.bind(Msg.BUNDLE_UNINSTALLED_EXCEPTION, getBundleData().getLocation()));
+ return (getBundleData().getStatus() & Constants.BUNDLE_STARTED) != 0;
+ }
+
+ public boolean isActivationPolicyUsed() {
+ if (getState() == Bundle.UNINSTALLED)
+ throw new IllegalArgumentException(NLS.bind(Msg.BUNDLE_UNINSTALLED_EXCEPTION, getBundleData().getLocation()));
+ return (getBundleData().getStatus() & Constants.BUNDLE_ACTIVATION_POLICY) != 0;
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/AliasMapper.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/AliasMapper.java
new file mode 100644
index 000000000..728745362
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/AliasMapper.java
@@ -0,0 +1,155 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.framework.internal.core;
+
+import java.io.*;
+import java.util.*;
+import org.eclipse.osgi.framework.debug.Debug;
+
+/**
+ * This class maps aliases.
+ */
+public class AliasMapper {
+ private static Map<String, Object> processorAliasTable;
+ private static Map<String, Object> osnameAliasTable;
+
+ // Safe lazy initialization
+ private static synchronized Map<String, Object> getProcessorAliasTable() {
+ if (processorAliasTable == null) {
+ InputStream in = AliasMapper.class.getResourceAsStream(Constants.OSGI_PROCESSOR_ALIASES);
+ if (in != null) {
+ try {
+ processorAliasTable = initAliases(in);
+ } finally {
+ try {
+ in.close();
+ } catch (IOException ee) {
+ // nothing
+ }
+ }
+ }
+ }
+ return processorAliasTable;
+ }
+
+ // Safe lazy initialization
+ private static synchronized Map<String, Object> getOSNameAliasTable() {
+ if (osnameAliasTable == null) {
+ InputStream in = AliasMapper.class.getResourceAsStream(Constants.OSGI_OSNAME_ALIASES);
+ if (in != null) {
+ try {
+ osnameAliasTable = initAliases(in);
+ } finally {
+ try {
+ in.close();
+ } catch (IOException ee) {
+ // nothing
+ }
+ }
+ }
+ }
+ return osnameAliasTable;
+ }
+
+ /**
+ * Return the master alias for the processor.
+ *
+ * @param processor Input name
+ * @return aliased name (if any)
+ */
+ public String aliasProcessor(String processor) {
+ processor = processor.toLowerCase();
+ Map<String, Object> aliases = getProcessorAliasTable();
+ if (aliases != null) {
+ String alias = (String) aliases.get(processor);
+ if (alias != null) {
+ processor = alias;
+ }
+ }
+ return processor;
+ }
+
+ /**
+ * Return the master alias for the osname.
+ *
+ * @param osname Input name
+ * @return aliased name (if any)
+ */
+ public Object aliasOSName(String osname) {
+ osname = osname.toLowerCase();
+ Map<String, Object> aliases = getOSNameAliasTable();
+ if (aliases != null) {
+ Object aliasObject = aliases.get(osname);
+ //String alias = (String) osnameAliasTable.get(osname);
+ if (aliasObject != null)
+ if (aliasObject instanceof String) {
+ osname = (String) aliasObject;
+ } else {
+ return aliasObject;
+ }
+ }
+ return osname;
+ }
+
+ /**
+ * Read alias data and populate a Map.
+ *
+ * @param in InputStream from which to read alias data.
+ * @return Map of aliases.
+ */
+ protected static Map<String, Object> initAliases(InputStream in) {
+ Map<String, Object> aliases = new HashMap<String, Object>(37);
+ try {
+ BufferedReader br;
+ try {
+ br = new BufferedReader(new InputStreamReader(in, "UTF8")); //$NON-NLS-1$
+ } catch (UnsupportedEncodingException e) {
+ br = new BufferedReader(new InputStreamReader(in));
+ }
+ while (true) {
+ String line = br.readLine();
+ if (line == null) /* EOF */{
+ break; /* done */
+ }
+ Tokenizer tokenizer = new Tokenizer(line);
+ String master = tokenizer.getString("# \t"); //$NON-NLS-1$
+ if (master != null) {
+ aliases.put(master.toLowerCase(), master);
+ parseloop: while (true) {
+ String alias = tokenizer.getString("# \t"); //$NON-NLS-1$
+ if (alias == null) {
+ break parseloop;
+ }
+ String lowerCaseAlias = alias.toLowerCase();
+ Object storedMaster = aliases.get(lowerCaseAlias);
+ if (storedMaster == null) {
+ aliases.put(lowerCaseAlias, master);
+ } else if (storedMaster instanceof String) {
+ List<String> newMaster = new ArrayList<String>();
+ newMaster.add((String) storedMaster);
+ newMaster.add(master);
+ aliases.put(lowerCaseAlias, newMaster);
+ } else {
+ @SuppressWarnings("unchecked")
+ List<String> newMaster = ((List<String>) storedMaster);
+ newMaster.add(master);
+ }
+ }
+ }
+ }
+ } catch (IOException e) {
+ if (Debug.DEBUG_GENERAL) {
+ Debug.printStackTrace(e);
+ }
+ }
+ return aliases;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleContextImpl.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleContextImpl.java
new file mode 100644
index 000000000..a6581cd2d
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleContextImpl.java
@@ -0,0 +1,974 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.internal.core;
+
+import java.io.File;
+import java.io.InputStream;
+import java.security.*;
+import java.util.*;
+import org.eclipse.osgi.event.BatchBundleListener;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.framework.eventmgr.EventDispatcher;
+import org.eclipse.osgi.internal.profile.Profile;
+import org.eclipse.osgi.internal.serviceregistry.*;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.*;
+
+/**
+ * Bundle's execution context.
+ *
+ * This object is given out to bundles and provides the
+ * implementation to the BundleContext for a host bundle.
+ * It is destroyed when a bundle is stopped.
+ */
+
+public class BundleContextImpl implements BundleContext, EventDispatcher<Object, Object, Object> {
+ private static boolean SET_TCCL = "true".equals(FrameworkProperties.getProperty("eclipse.bundle.setTCCL", "true")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ /** true if the bundle context is still valid */
+ private volatile boolean valid;
+
+ /** Bundle object this context is associated with. */
+ // This slot is accessed directly by the Framework instead of using
+ // the getBundle() method because the Framework needs access to the bundle
+ // even when the context is invalid while the close method is being called.
+ final BundleHost bundle;
+
+ /** Internal framework object. */
+ final Framework framework;
+
+ /** Services that bundle is using. Key is ServiceRegistrationImpl,
+ Value is ServiceUse */
+ /* @GuardedBy("contextLock") */
+ private HashMap<ServiceRegistrationImpl<?>, ServiceUse<?>> servicesInUse;
+
+ /** The current instantiation of the activator. */
+ protected BundleActivator activator;
+
+ /** private object for locking */
+ private final Object contextLock = new Object();
+
+ /**
+ * Construct a BundleContext which wrappers the framework for a
+ * bundle
+ *
+ * @param bundle The bundle we are wrapping.
+ */
+ protected BundleContextImpl(BundleHost bundle) {
+ this.bundle = bundle;
+ valid = true;
+ framework = bundle.framework;
+ synchronized (contextLock) {
+ servicesInUse = null;
+ }
+ activator = null;
+ }
+
+ /**
+ * Destroy the wrapper. This is called when the bundle is stopped.
+ *
+ */
+ protected void close() {
+ valid = false; /* invalidate context */
+
+ final ServiceRegistry registry = framework.getServiceRegistry();
+
+ registry.removeAllServiceListeners(this);
+ framework.removeAllListeners(this);
+
+ /* service's registered by the bundle, if any, are unregistered. */
+ registry.unregisterServices(this);
+
+ /* service's used by the bundle, if any, are released. */
+ registry.releaseServicesInUse(this);
+
+ synchronized (contextLock) {
+ servicesInUse = null;
+ }
+ }
+
+ /**
+ * Retrieve the value of the named environment property.
+ *
+ * @param key The name of the requested property.
+ * @return The value of the requested property, or <code>null</code> if
+ * the property is undefined.
+ */
+ public String getProperty(String key) {
+ SecurityManager sm = System.getSecurityManager();
+
+ if (sm != null) {
+ sm.checkPropertyAccess(key);
+ }
+
+ return (framework.getProperty(key));
+ }
+
+ /**
+ * Retrieve the Bundle object for the context bundle.
+ *
+ * @return The context bundle's Bundle object.
+ */
+ public Bundle getBundle() {
+ checkValid();
+
+ return getBundleImpl();
+ }
+
+ public AbstractBundle getBundleImpl() {
+ return bundle;
+ }
+
+ public Bundle installBundle(String location) throws BundleException {
+ return installBundle(location, null);
+ }
+
+ public Bundle installBundle(String location, InputStream in) throws BundleException {
+ checkValid();
+ //note AdminPermission is checked after bundle is loaded
+ return framework.installBundle(location, in, this);
+ }
+
+ /**
+ * Retrieve the bundle that has the given unique identifier.
+ *
+ * @param id The identifier of the bundle to retrieve.
+ * @return A Bundle object, or <code>null</code>
+ * if the identifier doesn't match any installed bundle.
+ */
+ public Bundle getBundle(long id) {
+ return framework.getBundle(this, id);
+ }
+
+ public Bundle getBundle(String location) {
+ return framework.getBundleByLocation(location);
+ }
+
+ /**
+ * Retrieve the bundle that has the given location.
+ *
+ * @param location The location string of the bundle to retrieve.
+ * @return A Bundle object, or <code>null</code>
+ * if the location doesn't match any installed bundle.
+ */
+ public AbstractBundle getBundleByLocation(String location) {
+ return (framework.getBundleByLocation(location));
+ }
+
+ /**
+ * Retrieve a list of all installed bundles.
+ * The list is valid at the time
+ * of the call to getBundles, but the framework is a very dynamic
+ * environment and bundles can be installed or uninstalled at anytime.
+ *
+ * @return An array of {@link AbstractBundle} objects, one
+ * object per installed bundle.
+ */
+ public Bundle[] getBundles() {
+ return framework.getBundles(this);
+ }
+
+ /**
+ * Add a service listener with a filter.
+ * {@link ServiceListener}s are notified when a service has a lifecycle
+ * state change.
+ * See {@link #getServiceReferences(String, String) getServiceReferences}
+ * for a description of the filter syntax.
+ * The listener is added to the context bundle's list of listeners.
+ * See {@link #getBundle() getBundle()}
+ * for a definition of context bundle.
+ *
+ * <p>The listener is called if the filter criteria is met.
+ * To filter based upon the class of the service, the filter
+ * should reference the "objectClass" property.
+ * If the filter paramater is <code>null</code>, all services
+ * are considered to match the filter.
+ * <p>If the Java runtime environment supports permissions, then additional
+ * filtering is done.
+ * {@link AbstractBundle#hasPermission(Object) Bundle.hasPermission} is called for the
+ * bundle which defines the listener to validate that the listener has the
+ * {@link ServicePermission} permission to <code>"get"</code> the service
+ * using at least one of the named classes the service was registered under.
+ *
+ * @param listener The service listener to add.
+ * @param filter The filter criteria.
+ * @exception InvalidSyntaxException If the filter parameter contains
+ * an invalid filter string which cannot be parsed.
+ * @see ServiceEvent
+ * @see ServiceListener
+ * @exception java.lang.IllegalStateException
+ * If the bundle context has stopped.
+ */
+ public void addServiceListener(ServiceListener listener, String filter) throws InvalidSyntaxException {
+ checkValid();
+
+ if (listener == null) {
+ throw new IllegalArgumentException();
+ }
+ framework.getServiceRegistry().addServiceListener(this, listener, filter);
+ }
+
+ /**
+ * Add a service listener.
+ *
+ * <p>This method is the same as calling
+ * {@link #addServiceListener(ServiceListener, String)}
+ * with filter set to <code>null</code>.
+ *
+ * @see #addServiceListener(ServiceListener, String)
+ */
+ public void addServiceListener(ServiceListener listener) {
+ try {
+ addServiceListener(listener, null);
+ } catch (InvalidSyntaxException e) {
+ if (Debug.DEBUG_GENERAL) {
+ Debug.println("InvalidSyntaxException w/ null filter" + e.getMessage()); //$NON-NLS-1$
+ Debug.printStackTrace(e);
+ }
+ }
+ }
+
+ /**
+ * Remove a service listener.
+ * The listener is removed from the context bundle's list of listeners.
+ * See {@link #getBundle() getBundle()}
+ * for a definition of context bundle.
+ *
+ * <p>If this method is called with a listener which is not registered,
+ * then this method does nothing.
+ *
+ * @param listener The service listener to remove.
+ * @exception java.lang.IllegalStateException
+ * If the bundle context has stopped.
+ */
+ public void removeServiceListener(ServiceListener listener) {
+ checkValid();
+
+ if (listener == null) {
+ throw new IllegalArgumentException();
+ }
+ framework.getServiceRegistry().removeServiceListener(this, listener);
+ }
+
+ /**
+ * Add a bundle listener.
+ * {@link BundleListener}s are notified when a bundle has a lifecycle
+ * state change.
+ * The listener is added to the context bundle's list of listeners.
+ * See {@link #getBundle() getBundle()}
+ * for a definition of context bundle.
+ *
+ * @param listener The bundle listener to add.
+ * @exception java.lang.IllegalStateException
+ * If the bundle context has stopped.
+ * @see BundleEvent
+ * @see BundleListener
+ */
+ public void addBundleListener(BundleListener listener) {
+ checkValid();
+ if (listener == null) {
+ throw new IllegalArgumentException();
+ }
+
+ if (Debug.DEBUG_EVENTS) {
+ String listenerName = listener.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(listener)); //$NON-NLS-1$
+ Debug.println("addBundleListener[" + bundle + "](" + listenerName + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ framework.addBundleListener(listener, this);
+ }
+
+ /**
+ * Remove a bundle listener.
+ * The listener is removed from the context bundle's list of listeners.
+ * See {@link #getBundle() getBundle()}
+ * for a definition of context bundle.
+ *
+ * <p>If this method is called with a listener which is not registered,
+ * then this method does nothing.
+ *
+ * @param listener The bundle listener to remove.
+ * @exception java.lang.IllegalStateException
+ * If the bundle context has stopped.
+ */
+ public void removeBundleListener(BundleListener listener) {
+ checkValid();
+ if (listener == null) {
+ throw new IllegalArgumentException();
+ }
+
+ if (Debug.DEBUG_EVENTS) {
+ String listenerName = listener.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(listener)); //$NON-NLS-1$
+ Debug.println("removeBundleListener[" + bundle + "](" + listenerName + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ framework.removeBundleListener(listener, this);
+ }
+
+ /**
+ * Add a general framework listener.
+ * {@link FrameworkListener}s are notified of general framework events.
+ * The listener is added to the context bundle's list of listeners.
+ * See {@link #getBundle() getBundle()}
+ * for a definition of context bundle.
+ *
+ * @param listener The framework listener to add.
+ * @exception java.lang.IllegalStateException
+ * If the bundle context has stopped.
+ * @see FrameworkEvent
+ * @see FrameworkListener
+ */
+ public void addFrameworkListener(FrameworkListener listener) {
+ checkValid();
+ if (listener == null) {
+ throw new IllegalArgumentException();
+ }
+
+ if (Debug.DEBUG_EVENTS) {
+ String listenerName = listener.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(listener)); //$NON-NLS-1$
+ Debug.println("addFrameworkListener[" + bundle + "](" + listenerName + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ framework.addFrameworkListener(listener, this);
+ }
+
+ /**
+ * Remove a framework listener.
+ * The listener is removed from the context bundle's list of listeners.
+ * See {@link #getBundle() getBundle()}
+ * for a definition of context bundle.
+ *
+ * <p>If this method is called with a listener which is not registered,
+ * then this method does nothing.
+ *
+ * @param listener The framework listener to remove.
+ * @exception java.lang.IllegalStateException
+ * If the bundle context has stopped.
+ */
+ public void removeFrameworkListener(FrameworkListener listener) {
+ checkValid();
+ if (listener == null) {
+ throw new IllegalArgumentException();
+ }
+
+ if (Debug.DEBUG_EVENTS) {
+ String listenerName = listener.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(listener)); //$NON-NLS-1$
+ Debug.println("removeFrameworkListener[" + bundle + "](" + listenerName + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ framework.removeFrameworkListener(listener, this);
+ }
+
+ /**
+ * Register a service with multiple names.
+ * This method registers the given service object with the given properties
+ * under the given class names.
+ * A {@link ServiceRegistration} object is returned.
+ * The {@link ServiceRegistration} object is for the private use of the bundle
+ * registering the service and should not be shared with other bundles.
+ * The registering bundle is defined to be the context bundle.
+ * See {@link #getBundle()} for a definition of context bundle.
+ * Other bundles can locate the service by using either the
+ * {@link #getServiceReferences getServiceReferences} or
+ * {@link #getServiceReference getServiceReference} method.
+ *
+ * <p>A bundle can register a service object that implements the
+ * {@link ServiceFactory} interface to
+ * have more flexiblity in providing service objects to different
+ * bundles.
+ *
+ * <p>The following steps are followed to register a service:
+ * <ol>
+ * <li>If the service parameter is not a {@link ServiceFactory},
+ * an <code>IllegalArgumentException</code> is thrown if the
+ * service parameter is not an <code>instanceof</code>
+ * all the classes named.
+ * <li>The service is added to the framework's service registry
+ * and may now be used by other bundles.
+ * <li>A {@link ServiceEvent} of type {@link ServiceEvent#REGISTERED}
+ * is synchronously sent.
+ * <li>A {@link ServiceRegistration} object for this registration
+ * is returned.
+ * </ol>
+ *
+ * @param clazzes The class names under which the service can be located.
+ * The class names in this array will be stored in the service's
+ * properties under the key "objectClass".
+ * @param service The service object or a {@link ServiceFactory} object.
+ * @param properties The properties for this service.
+ * The keys in the properties object must all be Strings.
+ * Changes should not be made to this object after calling this method.
+ * To update the service's properties call the
+ * {@link ServiceRegistration#setProperties ServiceRegistration.setProperties}
+ * method.
+ * This parameter may be <code>null</code> if the service has no properties.
+ * @return A {@link ServiceRegistration} object for use by the bundle
+ * registering the service to update the
+ * service's properties or to unregister the service.
+ * @exception java.lang.IllegalArgumentException If one of the following is true:
+ * <ul>
+ * <li>The service parameter is null.
+ * <li>The service parameter is not a {@link ServiceFactory} and is not an
+ * <code>instanceof</code> all the named classes in the clazzes parameter.
+ * </ul>
+ * @exception java.lang.SecurityException If the caller does not have
+ * {@link ServicePermission} permission to "register" the service for
+ * all the named classes
+ * and the Java runtime environment supports permissions.
+ * @exception java.lang.IllegalStateException
+ * If the bundle context has stopped.
+ * @see ServiceRegistration
+ * @see ServiceFactory
+ */
+ public ServiceRegistration<?> registerService(String[] clazzes, Object service, Dictionary<String, ?> properties) {
+ checkValid();
+ return framework.getServiceRegistry().registerService(this, clazzes, service, properties);
+ }
+
+ /**
+ * Register a service with a single name.
+ * This method registers the given service object with the given properties
+ * under the given class name.
+ *
+ * <p>This method is otherwise identical to
+ * {@link #registerService(java.lang.String[], java.lang.Object, java.util.Dictionary)}
+ * and is provided as a convenience when the service parameter will only be registered
+ * under a single class name.
+ *
+ * @see #registerService(java.lang.String[], java.lang.Object, java.util.Dictionary)
+ */
+ public ServiceRegistration<?> registerService(String clazz, Object service, Dictionary<String, ?> properties) {
+ String[] clazzes = new String[] {clazz};
+
+ return registerService(clazzes, service, properties);
+ }
+
+ /**
+ * Returns a list of <tt>ServiceReference</tt> objects. This method returns a list of
+ * <tt>ServiceReference</tt> objects for services which implement and were registered under
+ * the specified class and match the specified filter criteria.
+ *
+ * <p>The list is valid at the time of the call to this method, however as the Framework is
+ * a very dynamic environment, services can be modified or unregistered at anytime.
+ *
+ * <p><tt>filter</tt> is used to select the registered service whose
+ * properties objects contain keys and values which satisfy the filter.
+ * See {@link Filter}for a description of the filter string syntax.
+ *
+ * <p>If <tt>filter</tt> is <tt>null</tt>, all registered services
+ * are considered to match the filter.
+ * <p>If <tt>filter</tt> cannot be parsed, an {@link InvalidSyntaxException} will
+ * be thrown with a human readable message where the filter became unparsable.
+ *
+ * <p>The following steps are required to select a service:
+ * <ol>
+ * <li>If the Java Runtime Environment supports permissions, the caller is checked for the
+ * <tt>ServicePermission</tt> to get the service with the specified class.
+ * If the caller does not have the correct permission, <tt>null</tt> is returned.
+ * <li>If the filter string is not <tt>null</tt>, the filter string is
+ * parsed and the set of registered services which satisfy the filter is
+ * produced.
+ * If the filter string is <tt>null</tt>, then all registered services
+ * are considered to satisfy the filter.
+ * <li>If <code>clazz</code> is not <tt>null</tt>, the set is further reduced to
+ * those services which are an <tt>instanceof</tt> and were registered under the specified class.
+ * The complete list of classes of which a service is an instance and which
+ * were specified when the service was registered is available from the
+ * service's {@link Constants#OBJECTCLASS}property.
+ * <li>An array of <tt>ServiceReference</tt> to the selected services is returned.
+ * </ol>
+ *
+ * @param clazz The class name with which the service was registered, or
+ * <tt>null</tt> for all services.
+ * @param filter The filter criteria.
+ * @return An array of <tt>ServiceReference</tt> objects, or
+ * <tt>null</tt> if no services are registered which satisfy the search.
+ * @exception InvalidSyntaxException If <tt>filter</tt> contains
+ * an invalid filter string which cannot be parsed.
+ */
+ public ServiceReference<?>[] getServiceReferences(String clazz, String filter) throws InvalidSyntaxException {
+ checkValid();
+ return framework.getServiceRegistry().getServiceReferences(this, clazz, filter, false);
+ }
+
+ public ServiceReference<?>[] getAllServiceReferences(String clazz, String filter) throws InvalidSyntaxException {
+ checkValid();
+ return framework.getServiceRegistry().getServiceReferences(this, clazz, filter, true);
+ }
+
+ /**
+ * Get a service reference.
+ * Retrieves a {@link ServiceReference} for a service
+ * which implements the named class.
+ *
+ * <p>This reference is valid at the time
+ * of the call to this method, but since the framework is a very dynamic
+ * environment, services can be modified or unregistered at anytime.
+ *
+ * <p>This method is provided as a convenience for when the caller is
+ * interested in any service which implements a named class. This method is
+ * the same as calling {@link #getServiceReferences getServiceReferences}
+ * with a <code>null</code> filter string but only a single {@link ServiceReference}
+ * is returned.
+ *
+ * @param clazz The class name which the service must implement.
+ * @return A {@link ServiceReference} object, or <code>null</code>
+ * if no services are registered which implement the named class.
+ * @see #getServiceReferences
+ */
+ public ServiceReference<?> getServiceReference(String clazz) {
+ checkValid();
+
+ return framework.getServiceRegistry().getServiceReference(this, clazz);
+ }
+
+ /**
+ * Get a service's service object.
+ * Retrieves the service object for a service.
+ * A bundle's use of a service is tracked by a
+ * use count. Each time a service's service object is returned by
+ * {@link #getService}, the context bundle's use count for the service
+ * is incremented by one. Each time the service is release by
+ * {@link #ungetService}, the context bundle's use count
+ * for the service is decremented by one.
+ * When a bundle's use count for a service
+ * drops to zero, the bundle should no longer use the service.
+ * See {@link #getBundle()} for a definition of context bundle.
+ *
+ * <p>This method will always return <code>null</code> when the
+ * service associated with this reference has been unregistered.
+ *
+ * <p>The following steps are followed to get the service object:
+ * <ol>
+ * <li>If the service has been unregistered,
+ * <code>null</code> is returned.
+ * <li>The context bundle's use count for this service is incremented by one.
+ * <li>If the context bundle's use count for the service is now one and
+ * the service was registered with a {@link ServiceFactory},
+ * the {@link ServiceFactory#getService ServiceFactory.getService} method
+ * is called to create a service object for the context bundle.
+ * This service object is cached by the framework.
+ * While the context bundle's use count for the service is greater than zero,
+ * subsequent calls to get the services's service object for the context bundle
+ * will return the cached service object.
+ * <br>If the service object returned by the {@link ServiceFactory}
+ * is not an <code>instanceof</code>
+ * all the classes named when the service was registered or
+ * the {@link ServiceFactory} throws an exception,
+ * <code>null</code> is returned and a
+ * {@link FrameworkEvent} of type {@link FrameworkEvent#ERROR} is broadcast.
+ * <li>The service object for the service is returned.
+ * </ol>
+ *
+ * @param reference A reference to the service whose service object is desired.
+ * @return A service object for the service associated with this
+ * reference, or <code>null</code> if the service is not registered.
+ * @exception java.lang.SecurityException If the caller does not have
+ * {@link ServicePermission} permission to "get" the service
+ * using at least one of the named classes the service was registered under
+ * and the Java runtime environment supports permissions.
+ * @exception java.lang.IllegalStateException
+ * If the bundle context has stopped.
+ * @see #ungetService
+ * @see ServiceFactory
+ */
+ public <S> S getService(ServiceReference<S> reference) {
+ checkValid();
+ if (reference == null)
+ throw new NullPointerException("A null service reference is not allowed."); //$NON-NLS-1$
+ synchronized (contextLock) {
+ if (servicesInUse == null)
+ // Cannot predict how many services a bundle will use, start with a small table.
+ servicesInUse = new HashMap<ServiceRegistrationImpl<?>, ServiceUse<?>>(10);
+ }
+
+ @SuppressWarnings("unchecked")
+ S service = (S) framework.getServiceRegistry().getService(this, (ServiceReferenceImpl<S>) reference);
+ return service;
+ }
+
+ /**
+ * Unget a service's service object.
+ * Releases the service object for a service.
+ * If the context bundle's use count for the service is zero, this method
+ * returns <code>false</code>. Otherwise, the context bundle's use count for the
+ * service is decremented by one.
+ * See {@link #getBundle()} for a definition of context bundle.
+ *
+ * <p>The service's service object
+ * should no longer be used and all references to it should be destroyed
+ * when a bundle's use count for the service
+ * drops to zero.
+ *
+ * <p>The following steps are followed to unget the service object:
+ * <ol>
+ * <li>If the context bundle's use count for the service is zero or
+ * the service has been unregistered,
+ * <code>false</code> is returned.
+ * <li>The context bundle's use count for this service is decremented by one.
+ * <li>If the context bundle's use count for the service is now zero and
+ * the service was registered with a {@link ServiceFactory},
+ * the {@link ServiceFactory#ungetService ServiceFactory.ungetService} method
+ * is called to release the service object for the context bundle.
+ * <li><code>true</code> is returned.
+ * </ol>
+ *
+ * @param reference A reference to the service to be released.
+ * @return <code>false</code> if the context bundle's use count for the service
+ * is zero or if the service has been unregistered,
+ * otherwise <code>true</code>.
+ * @exception java.lang.IllegalStateException
+ * If the bundle context has stopped.
+ * @see #getService
+ * @see ServiceFactory
+ */
+ public boolean ungetService(ServiceReference<?> reference) {
+ checkValid();
+
+ return framework.getServiceRegistry().ungetService(this, (ServiceReferenceImpl<?>) reference);
+ }
+
+ /**
+ * Creates a <code>File</code> object for a file in the
+ * persistent storage area provided for the bundle by the framework.
+ * If the adaptor does not have file system support, this method will
+ * return <code>null</code>.
+ *
+ * <p>A <code>File</code> object for the base directory of the
+ * persistent storage area provided for the context bundle by the framework
+ * can be obtained by calling this method with the empty string ("")
+ * as the parameter.
+ * See {@link #getBundle()} for a definition of context bundle.
+ *
+ * <p>If the Java runtime environment supports permissions,
+ * the framework the will ensure that the bundle has
+ * <code>java.io.FilePermission</code> with actions
+ * "read","write","execute","delete" for all files (recursively) in the
+ * persistent storage area provided for the context bundle by the framework.
+ *
+ * @param filename A relative name to the file to be accessed.
+ * @return A <code>File</code> object that represents the requested file or
+ * <code>null</code> if the adaptor does not have file system support.
+ * @exception java.lang.IllegalStateException
+ * If the bundle context has stopped.
+ */
+ public File getDataFile(String filename) {
+ checkValid();
+
+ return (framework.getDataFile(bundle, filename));
+ }
+
+ /**
+ * Call bundle's BundleActivator.start()
+ * This method is called by Bundle.startWorker to start the bundle.
+ *
+ * @exception BundleException if
+ * the bundle has a class that implements the BundleActivator interface,
+ * but Framework couldn't instantiate it, or the BundleActivator.start()
+ * method failed
+ */
+ protected void start() throws BundleException {
+ activator = bundle.loadBundleActivator();
+
+ if (activator != null) {
+ try {
+ startActivator(activator);
+ } catch (BundleException be) {
+ activator = null;
+ throw be;
+ }
+ }
+
+ /* activator completed successfully. We must use this
+ same activator object when we stop this bundle. */
+ }
+
+ /**
+ * Calls the start method of a BundleActivator.
+ * @param bundleActivator that activator to start
+ */
+ protected void startActivator(final BundleActivator bundleActivator) throws BundleException {
+ if (Profile.PROFILE && Profile.STARTUP)
+ Profile.logEnter("BundleContextImpl.startActivator()", null); //$NON-NLS-1$
+ try {
+ AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
+ public Object run() throws Exception {
+ if (bundleActivator != null) {
+ if (Profile.PROFILE && Profile.STARTUP)
+ Profile.logTime("BundleContextImpl.startActivator()", "calling " + bundle.getLocation() + " bundle activator"); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
+ // make sure the context class loader is set correctly
+ Object previousTCCL = setContextFinder();
+ /* Start the bundle synchronously */
+ try {
+ bundleActivator.start(BundleContextImpl.this);
+ } finally {
+ if (previousTCCL != Boolean.FALSE)
+ Thread.currentThread().setContextClassLoader((ClassLoader) previousTCCL);
+ }
+ if (Profile.PROFILE && Profile.STARTUP)
+ Profile.logTime("BundleContextImpl.startActivator()", "returned from " + bundle.getLocation() + " bundle activator"); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
+ }
+ return null;
+ }
+ });
+ } catch (Throwable t) {
+ if (t instanceof PrivilegedActionException) {
+ t = ((PrivilegedActionException) t).getException();
+ }
+
+ if (Debug.DEBUG_GENERAL) {
+ Debug.printStackTrace(t);
+ }
+
+ String clazz = null;
+ clazz = bundleActivator.getClass().getName();
+
+ throw new BundleException(NLS.bind(Msg.BUNDLE_ACTIVATOR_EXCEPTION, new Object[] {clazz, "start", bundle.getSymbolicName() == null ? "" + bundle.getBundleId() : bundle.getSymbolicName()}), BundleException.ACTIVATOR_ERROR, t); //$NON-NLS-1$ //$NON-NLS-2$
+ } finally {
+ if (Profile.PROFILE && Profile.STARTUP)
+ Profile.logExit("BundleContextImpl.startActivator()"); //$NON-NLS-1$
+ }
+ }
+
+ Object setContextFinder() {
+ if (!SET_TCCL)
+ return Boolean.FALSE;
+ Thread currentThread = Thread.currentThread();
+ ClassLoader previousTCCL = currentThread.getContextClassLoader();
+ ClassLoader contextFinder = framework.getContextFinder();
+ if (previousTCCL != contextFinder) {
+ currentThread.setContextClassLoader(framework.getContextFinder());
+ return previousTCCL;
+ }
+ return Boolean.FALSE;
+ }
+
+ /**
+ * Call bundle's BundleActivator.stop()
+ * This method is called by Bundle.stopWorker to stop the bundle.
+ *
+ * @exception BundleException if
+ * the bundle has a class that implements the BundleActivator interface,
+ * and the BundleActivator.stop() method failed
+ */
+ protected void stop() throws BundleException {
+ try {
+ AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
+ public Object run() throws Exception {
+ if (activator != null) {
+ // make sure the context class loader is set correctly
+ Object previousTCCL = setContextFinder();
+ try {
+ /* Stop the bundle synchronously */
+ activator.stop(BundleContextImpl.this);
+ } finally {
+ if (previousTCCL != Boolean.FALSE)
+ Thread.currentThread().setContextClassLoader((ClassLoader) previousTCCL);
+ }
+ }
+ return null;
+ }
+ });
+ } catch (Throwable t) {
+ if (t instanceof PrivilegedActionException) {
+ t = ((PrivilegedActionException) t).getException();
+ }
+
+ if (Debug.DEBUG_GENERAL) {
+ Debug.printStackTrace(t);
+ }
+
+ String clazz = (activator == null) ? "" : activator.getClass().getName(); //$NON-NLS-1$
+
+ throw new BundleException(NLS.bind(Msg.BUNDLE_ACTIVATOR_EXCEPTION, new Object[] {clazz, "stop", bundle.getSymbolicName() == null ? "" + bundle.getBundleId() : bundle.getSymbolicName()}), BundleException.ACTIVATOR_ERROR, t); //$NON-NLS-1$ //$NON-NLS-2$
+ } finally {
+ activator = null;
+ }
+ }
+
+ /**
+ * Return the map of ServiceRegistrationImpl to ServiceUse for services being
+ * used by this context.
+ * @return A map of ServiceRegistrationImpl to ServiceUse for services in use by
+ * this context.
+ */
+ public Map<ServiceRegistrationImpl<?>, ServiceUse<?>> getServicesInUseMap() {
+ synchronized (contextLock) {
+ return servicesInUse;
+ }
+ }
+
+ /**
+ * Bottom level event dispatcher for the BundleContext.
+ *
+ * @param originalListener listener object registered under.
+ * @param l listener to call (may be filtered).
+ * @param action Event class type
+ * @param object Event object
+ */
+ public void dispatchEvent(Object originalListener, Object l, int action, Object object) {
+ // save the bundle ref to a local variable
+ // to avoid interference from another thread closing this context
+ AbstractBundle tmpBundle = bundle;
+ Object previousTCCL = setContextFinder();
+ try {
+ if (isValid()) /* if context still valid */{
+ switch (action) {
+ case Framework.BUNDLEEVENT :
+ case Framework.BUNDLEEVENTSYNC : {
+ BundleListener listener = (BundleListener) l;
+
+ if (Debug.DEBUG_EVENTS) {
+ String listenerName = listener.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(listener)); //$NON-NLS-1$
+ Debug.println("dispatchBundleEvent[" + tmpBundle + "](" + listenerName + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ BundleEvent event = (BundleEvent) object;
+ switch (event.getType()) {
+ case Framework.BATCHEVENT_BEGIN : {
+ if (listener instanceof BatchBundleListener)
+ ((BatchBundleListener) listener).batchBegin();
+ break;
+ }
+ case Framework.BATCHEVENT_END : {
+ if (listener instanceof BatchBundleListener)
+ ((BatchBundleListener) listener).batchEnd();
+ break;
+ }
+ default : {
+ listener.bundleChanged((BundleEvent) object);
+ }
+ }
+ break;
+ }
+
+ case ServiceRegistry.SERVICEEVENT : {
+ ServiceEvent event = (ServiceEvent) object;
+
+ ServiceListener listener = (ServiceListener) l;
+ if (Debug.DEBUG_EVENTS) {
+ String listenerName = listener.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(listener)); //$NON-NLS-1$
+ Debug.println("dispatchServiceEvent[" + tmpBundle + "](" + listenerName + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ listener.serviceChanged(event);
+
+ break;
+ }
+
+ case Framework.FRAMEWORKEVENT : {
+ FrameworkListener listener = (FrameworkListener) l;
+
+ if (Debug.DEBUG_EVENTS) {
+ String listenerName = listener.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(listener)); //$NON-NLS-1$
+ Debug.println("dispatchFrameworkEvent[" + tmpBundle + "](" + listenerName + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ listener.frameworkEvent((FrameworkEvent) object);
+ break;
+ }
+ default : {
+ throw new InternalError();
+ }
+ }
+ }
+ } catch (Throwable t) {
+ if (Debug.DEBUG_GENERAL) {
+ Debug.println("Exception in bottom level event dispatcher: " + t.getMessage()); //$NON-NLS-1$
+ Debug.printStackTrace(t);
+ }
+ // allow the adaptor to handle this unexpected error
+ framework.adaptor.handleRuntimeError(t);
+ publisherror: {
+ if (action == Framework.FRAMEWORKEVENT) {
+ FrameworkEvent event = (FrameworkEvent) object;
+ if (event.getType() == FrameworkEvent.ERROR) {
+ break publisherror; // avoid infinite loop
+ }
+ }
+
+ framework.publishFrameworkEvent(FrameworkEvent.ERROR, tmpBundle, t);
+ }
+ } finally {
+ if (previousTCCL != Boolean.FALSE)
+ Thread.currentThread().setContextClassLoader((ClassLoader) previousTCCL);
+ }
+ }
+
+ /**
+ * Construct a Filter object. This filter object may be used
+ * to match a ServiceReference or a Dictionary.
+ * See Filter
+ * for a description of the filter string syntax.
+ *
+ * @param filter The filter string.
+ * @return A Filter object encapsulating the filter string.
+ * @exception InvalidSyntaxException If the filter parameter contains
+ * an invalid filter string which cannot be parsed.
+ */
+ public Filter createFilter(String filter) throws InvalidSyntaxException {
+ checkValid();
+
+ return FilterImpl.newInstance(filter);
+ }
+
+ /**
+ * This method checks that the context is still valid. If the context is
+ * no longer valid, an IllegalStateException is thrown.
+ *
+ * @exception java.lang.IllegalStateException
+ * If the context bundle has stopped.
+ */
+ public void checkValid() {
+ if (!isValid()) {
+ throw new IllegalStateException(Msg.BUNDLE_CONTEXT_INVALID_EXCEPTION);
+ }
+ }
+
+ /**
+ * This method checks that the context is still valid.
+ *
+ * @return true if the context is still valid; false otherwise
+ */
+ protected boolean isValid() {
+ return valid;
+ }
+
+ public Framework getFramework() {
+ return framework;
+ }
+
+ public <S> ServiceRegistration<S> registerService(Class<S> clazz, S service, Dictionary<String, ?> properties) {
+ @SuppressWarnings("unchecked")
+ ServiceRegistration<S> registration = (ServiceRegistration<S>) registerService(clazz.getName(), service, properties);
+ return registration;
+ }
+
+ public <S> ServiceReference<S> getServiceReference(Class<S> clazz) {
+ @SuppressWarnings("unchecked")
+ ServiceReference<S> reference = (ServiceReference<S>) getServiceReference(clazz.getName());
+ return reference;
+ }
+
+ public <S> Collection<ServiceReference<S>> getServiceReferences(Class<S> clazz, String filter) throws InvalidSyntaxException {
+ @SuppressWarnings("unchecked")
+ ServiceReference<S>[] refs = (ServiceReference<S>[]) getServiceReferences(clazz.getName(), filter);
+ if (refs == null) {
+ @SuppressWarnings("unchecked")
+ Collection<ServiceReference<S>> empty = Collections.EMPTY_LIST;
+ return empty;
+ }
+ List<ServiceReference<S>> result = new ArrayList<ServiceReference<S>>(refs.length);
+ for (ServiceReference<S> b : refs) {
+ result.add(b);
+ }
+ return result;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleFragment.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleFragment.java
new file mode 100644
index 000000000..98ca8ee24
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleFragment.java
@@ -0,0 +1,334 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.internal.core;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Enumeration;
+import org.eclipse.osgi.framework.adaptor.BundleData;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.internal.loader.BundleLoader;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.*;
+
+public class BundleFragment extends AbstractBundle {
+
+ /** The resolved host that this fragment is attached to */
+ protected BundleHost[] hosts;
+
+ /**
+ * @param bundledata
+ * @param framework
+ * @throws BundleException
+ */
+ public BundleFragment(BundleData bundledata, Framework framework) throws BundleException {
+ super(bundledata, framework);
+ hosts = null;
+ }
+
+ /**
+ * Load the bundle.
+ */
+ protected void load() {
+ if (Debug.DEBUG_GENERAL) {
+ if ((state & (INSTALLED)) == 0) {
+ Debug.println("Bundle.load called when state != INSTALLED: " + this); //$NON-NLS-1$
+ Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$
+ }
+ }
+
+ if (framework.isActive()) {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null && framework.securityAdmin != null) {
+ domain = framework.securityAdmin.createProtectionDomain(this);
+ }
+ }
+ }
+
+ /**
+ * Reload from a new bundle.
+ * This method must be called while holding the bundles lock.
+ *
+ * @param newBundle Dummy Bundle which contains new data.
+ * @return true if an exported package is "in use". i.e. it has been imported by a bundle
+ */
+ protected boolean reload(AbstractBundle newBundle) {
+ if (Debug.DEBUG_GENERAL) {
+ if ((state & (INSTALLED | RESOLVED)) == 0) {
+ Debug.println("Bundle.reload called when state != INSTALLED | RESOLVED: " + this); //$NON-NLS-1$
+ Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$
+ }
+ }
+
+ boolean exporting = false;
+ if (framework.isActive()) {
+ if (hosts != null) {
+ if (state == RESOLVED) {
+ exporting = true; // if we have a host we cannot be removed until the host is refreshed
+ hosts = null;
+ state = INSTALLED;
+ }
+ }
+ } else {
+ /* close the outgoing jarfile */
+ try {
+ this.bundledata.close();
+ } catch (IOException e) {
+ // Do Nothing
+ }
+ }
+ if (!exporting) {
+ /* close the outgoing jarfile */
+ try {
+ this.bundledata.close();
+ } catch (IOException e) {
+ // Do Nothing
+ }
+ }
+
+ this.bundledata = newBundle.bundledata;
+ this.bundledata.setBundle(this);
+ // create a new domain for the bundle because its signers/symbolic-name may have changed
+ if (framework.isActive() && System.getSecurityManager() != null && framework.securityAdmin != null)
+ domain = framework.securityAdmin.createProtectionDomain(this);
+ return (exporting);
+ }
+
+ /**
+ * Refresh the bundle. This is called by Framework.refreshPackages.
+ * This method must be called while holding the bundles lock.
+ * this.loader.unimportPackages must have already been called before calling
+ * this method!
+ */
+ protected void refresh() {
+ if (Debug.DEBUG_GENERAL) {
+ if ((state & (UNINSTALLED | INSTALLED | RESOLVED)) == 0) {
+ Debug.println("Bundle.refresh called when state != UNINSTALLED | INSTALLED | RESOLVED: " + this); //$NON-NLS-1$
+ Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$
+ }
+ }
+
+ if (state == RESOLVED) {
+ hosts = null;
+ state = INSTALLED;
+ // Do not publish UNRESOLVED event here. This is done by caller
+ // to resolve if appropriate.
+ }
+ manifestLocalization = null;
+ }
+
+ /**
+ * Unload the bundle.
+ * This method must be called while holding the bundles lock.
+ *
+ * @return true if an exported package is "in use". i.e. it has been imported by a bundle
+ */
+ protected boolean unload() {
+ if (Debug.DEBUG_GENERAL) {
+ if ((state & (UNINSTALLED | INSTALLED | RESOLVED)) == 0) {
+ Debug.println("Bundle.unload called when state != UNINSTALLED | INSTALLED | RESOLVED: " + this); //$NON-NLS-1$
+ Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$
+ }
+ }
+
+ boolean exporting = false;
+ if (framework.isActive()) {
+ if (hosts != null) {
+ if (state == RESOLVED) {
+ exporting = true; // if we have a host we cannot be removed until the host is refreshed
+ hosts = null;
+ state = INSTALLED;
+ }
+ domain = null;
+ }
+ }
+ if (!exporting) {
+ try {
+ this.bundledata.close();
+ } catch (IOException e) { // Do Nothing.
+ }
+ }
+
+ return (exporting);
+ }
+
+ /**
+ * This method loads a class from the bundle.
+ *
+ * @param name the name of the desired Class.
+ * @param checkPermission indicates whether a permission check should be done.
+ * @return the resulting Class
+ * @exception java.lang.ClassNotFoundException if the class definition was not found.
+ */
+ protected Class<?> loadClass(String name, boolean checkPermission) throws ClassNotFoundException {
+ if (checkPermission) {
+ try {
+ framework.checkAdminPermission(this, AdminPermission.CLASS);
+ } catch (SecurityException e) {
+ throw new ClassNotFoundException(name, e);
+ }
+ checkValid();
+ }
+ // cannot load a class from a fragment because there is no classloader
+ // associated with fragments.
+ throw new ClassNotFoundException(NLS.bind(Msg.BUNDLE_FRAGMENT_CNFE, name));
+ }
+
+ /**
+ * Find the specified resource in this bundle.
+ *
+ * This bundle's class loader is called to search for the named resource.
+ * If this bundle's state is <tt>INSTALLED</tt>, then only this bundle will
+ * be searched for the specified resource. Imported packages cannot be searched
+ * when a bundle has not been resolved.
+ *
+ * @param name The name of the resource.
+ * See <tt>java.lang.ClassLoader.getResource</tt> for a description of
+ * the format of a resource name.
+ * @return a URL to the named resource, or <tt>null</tt> if the resource could
+ * not be found or if the caller does not have
+ * the <tt>AdminPermission</tt>, and the Java Runtime Environment supports permissions.
+ *
+ * @exception java.lang.IllegalStateException If this bundle has been uninstalled.
+ */
+ public URL getResource(String name) {
+ checkValid();
+ // cannot get a resource for a fragment because there is no classloader
+ // associated with fragments.
+ return (null);
+
+ }
+
+ public Enumeration<URL> getResources(String name) {
+ checkValid();
+ // cannot get a resource for a fragment because there is no classloader
+ // associated with fragments.
+ return null;
+ }
+
+ /**
+ * Internal worker to start a bundle.
+ *
+ * @param options
+ */
+ protected void startWorker(int options) throws BundleException {
+ throw new BundleException(NLS.bind(Msg.BUNDLE_FRAGMENT_START, this), BundleException.INVALID_OPERATION);
+ }
+
+ /**
+ * Internal worker to stop a bundle.
+ *
+ * @param options
+ */
+ protected void stopWorker(int options) throws BundleException {
+ throw new BundleException(NLS.bind(Msg.BUNDLE_FRAGMENT_STOP, this), BundleException.INVALID_OPERATION);
+ }
+
+ /**
+ * Provides a list of {@link ServiceReference}s for the services
+ * registered by this bundle
+ * or <code>null</code> if the bundle has no registered
+ * services.
+ *
+ * <p>The list is valid at the time
+ * of the call to this method, but the framework is a very dynamic
+ * environment and services can be modified or unregistered at anytime.
+ *
+ * @return An array of {@link ServiceReference} or <code>null</code>.
+ * @exception java.lang.IllegalStateException If the
+ * bundle has been uninstalled.
+ * @see ServiceRegistration
+ * @see ServiceReference
+ */
+ public ServiceReference<?>[] getRegisteredServices() {
+ checkValid();
+ // Fragments cannot have a BundleContext and therefore
+ // cannot have any services registered.
+ return null;
+ }
+
+ /**
+ * Provides a list of {@link ServiceReference}s for the
+ * services this bundle is using,
+ * or <code>null</code> if the bundle is not using any services.
+ * A bundle is considered to be using a service if the bundle's
+ * use count for the service is greater than zero.
+ *
+ * <p>The list is valid at the time
+ * of the call to this method, but the framework is a very dynamic
+ * environment and services can be modified or unregistered at anytime.
+ *
+ * @return An array of {@link ServiceReference} or <code>null</code>.
+ * @exception java.lang.IllegalStateException If the
+ * bundle has been uninstalled.
+ * @see ServiceReference
+ */
+ public ServiceReference<?>[] getServicesInUse() {
+ checkValid();
+ // Fragments cannot have a BundleContext and therefore
+ // cannot have any services in use.
+ return null;
+ }
+
+ synchronized BundleHost[] getHosts() {
+ return hosts;
+ }
+
+ protected boolean isFragment() {
+ return true;
+ }
+
+ /**
+ * Adds a host bundle for this fragment.
+ * @param host the BundleHost to add to the set of host bundles
+ */
+ boolean addHost(BundleHost host) {
+ if (host == null)
+ return false;
+ try {
+ host.attachFragment(this);
+ } catch (BundleException be) {
+ framework.publishFrameworkEvent(FrameworkEvent.ERROR, host, be);
+ return false;
+ }
+ synchronized (this) {
+ if (hosts == null) {
+ hosts = new BundleHost[] {host};
+ return true;
+ }
+ for (int i = 0; i < hosts.length; i++) {
+ if (host == hosts[i])
+ return true; // already a host
+ }
+ BundleHost[] newHosts = new BundleHost[hosts.length + 1];
+ System.arraycopy(hosts, 0, newHosts, 0, hosts.length);
+ newHosts[newHosts.length - 1] = host;
+ hosts = newHosts;
+ }
+ return true;
+ }
+
+ protected BundleLoader getBundleLoader() {
+ // Fragments cannot have a BundleLoader.
+ return null;
+ }
+
+ /**
+ * Return the current context for this bundle.
+ *
+ * @return BundleContext for this bundle.
+ */
+ protected BundleContextImpl getContext() {
+ // Fragments cannot have a BundleContext.
+ return null;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleHost.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleHost.java
new file mode 100644
index 000000000..cfc55b0a3
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleHost.java
@@ -0,0 +1,686 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.internal.core;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Enumeration;
+import org.eclipse.osgi.framework.adaptor.*;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+import org.eclipse.osgi.internal.loader.BundleLoader;
+import org.eclipse.osgi.internal.loader.BundleLoaderProxy;
+import org.eclipse.osgi.service.resolver.BundleDescription;
+import org.eclipse.osgi.service.resolver.ResolverHookException;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.*;
+
+public class BundleHost extends AbstractBundle {
+ public static final int LAZY_TRIGGER = 0x40000000;
+ /**
+ * The BundleLoader proxy; a lightweight object that acts as a proxy
+ * to the BundleLoader and allows lazy creation of the BundleLoader object
+ */
+ private BundleLoaderProxy proxy;
+
+ /** The BundleContext that represents this Bundle and all of its fragments */
+ protected BundleContextImpl context;
+
+ /** The List of BundleFragments */
+ protected BundleFragment[] fragments;
+
+ public BundleHost(BundleData bundledata, Framework framework) {
+ super(bundledata, framework);
+ context = null;
+ fragments = null;
+ }
+
+ /**
+ * Load the bundle.
+ */
+ protected void load() {
+ if (Debug.DEBUG_GENERAL) {
+ if ((state & (INSTALLED)) == 0) {
+ Debug.println("Bundle.load called when state != INSTALLED: " + this); //$NON-NLS-1$
+ Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$
+ }
+ if (proxy != null) {
+ Debug.println("Bundle.load called when proxy != null: " + this); //$NON-NLS-1$
+ Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$
+ }
+ }
+
+ if (framework.isActive()) {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null && framework.securityAdmin != null) {
+ domain = framework.securityAdmin.createProtectionDomain(this);
+ }
+ }
+ proxy = null;
+ }
+
+ /**
+ * Reload from a new bundle.
+ * This method must be called while holding the bundles lock.
+ *
+ * @param newBundle Dummy Bundle which contains new data.
+ * @return true if an exported package is "in use". i.e. it has been imported by a bundle
+ */
+ protected boolean reload(AbstractBundle newBundle) {
+ if (Debug.DEBUG_GENERAL) {
+ if ((state & (INSTALLED | RESOLVED)) == 0) {
+ Debug.println("Bundle.reload called when state != INSTALLED | RESOLVED: " + this); //$NON-NLS-1$
+ Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$
+ }
+ }
+
+ boolean exporting = false;
+
+ if (framework.isActive()) {
+ if (state == RESOLVED) {
+ BundleLoaderProxy curProxy = getLoaderProxy();
+ exporting = curProxy.inUse();
+ if (exporting) {
+ // make sure the BundleLoader is created.
+ curProxy.getBundleLoader().createClassLoader();
+ } else
+ BundleLoader.closeBundleLoader(proxy);
+ state = INSTALLED;
+ proxy = null;
+ fragments = null;
+ }
+
+ } else {
+ /* close the outgoing jarfile */
+ try {
+ this.bundledata.close();
+ } catch (IOException e) {
+ // Do Nothing
+ }
+ }
+ this.bundledata = newBundle.bundledata;
+ this.bundledata.setBundle(this);
+ // create a new domain for the bundle because its signers/symbolic-name may have changed
+ if (framework.isActive() && System.getSecurityManager() != null && framework.securityAdmin != null)
+ domain = framework.securityAdmin.createProtectionDomain(this);
+ return (exporting);
+ }
+
+ /**
+ * Refresh the bundle. This is called by Framework.refreshPackages.
+ * This method must be called while holding the bundles lock.
+ */
+ protected void refresh() {
+ if (Debug.DEBUG_GENERAL) {
+ if ((state & (UNINSTALLED | INSTALLED | RESOLVED)) == 0) {
+ Debug.println("Bundle.reload called when state != UNINSTALLED | INSTALLED | RESOLVED: " + this); //$NON-NLS-1$
+ Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$
+ }
+ }
+ if (state == RESOLVED) {
+ BundleLoader.closeBundleLoader(proxy);
+ proxy = null;
+ fragments = null;
+ state = INSTALLED;
+ // Do not publish UNRESOLVED event here. This is done by caller
+ // to resolve if appropriate.
+ }
+ manifestLocalization = null;
+ }
+
+ /**
+ * Unload the bundle.
+ * This method must be called while holding the bundles lock.
+ *
+ * @return true if an exported package is "in use". i.e. it has been imported by a bundle
+ */
+ protected boolean unload() {
+ if (Debug.DEBUG_GENERAL) {
+ if ((state & (UNINSTALLED | INSTALLED | RESOLVED)) == 0) {
+ Debug.println("Bundle.unload called when state != UNINSTALLED | INSTALLED | RESOLVED: " + this); //$NON-NLS-1$
+ Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$
+ }
+ }
+
+ boolean exporting = false;
+
+ if (framework.isActive()) {
+ if (state == RESOLVED) {
+ BundleLoaderProxy curProxy = getLoaderProxy();
+ exporting = curProxy.inUse();
+ if (exporting) {
+ // make sure the BundleLoader is created.
+ curProxy.getBundleLoader().createClassLoader();
+ } else
+ BundleLoader.closeBundleLoader(proxy);
+
+ state = INSTALLED;
+ proxy = null;
+ fragments = null;
+ domain = null;
+ }
+ }
+ if (!exporting) {
+ try {
+ this.bundledata.close();
+ } catch (IOException e) { // Do Nothing.
+ }
+ }
+
+ return (exporting);
+ }
+
+ private BundleLoader checkLoader() {
+ checkValid();
+
+ // check to see if the bundle is resolved
+ if (!isResolved()) {
+ if (!framework.packageAdmin.resolveBundles(new Bundle[] {this})) {
+ return null;
+ }
+ }
+ if (Debug.DEBUG_GENERAL) {
+ if ((state & (STARTING | ACTIVE | STOPPING | RESOLVED)) == 0) {
+ Debug.println("Bundle.checkLoader() called when state != STARTING | ACTIVE | STOPPING | RESOLVED: " + this); //$NON-NLS-1$
+ Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$
+ }
+ }
+
+ BundleLoader loader = getBundleLoader();
+ if (loader == null) {
+ if (Debug.DEBUG_GENERAL) {
+ Debug.println("Bundle.checkLoader() called when loader == null: " + this); //$NON-NLS-1$
+ Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$
+ }
+ return null;
+ }
+ return loader;
+ }
+
+ /**
+ * This method loads a class from the bundle.
+ *
+ * @param name the name of the desired Class.
+ * @param checkPermission indicates whether a permission check should be done.
+ * @return the resulting Class
+ * @exception java.lang.ClassNotFoundException if the class definition was not found.
+ */
+ protected Class<?> loadClass(String name, boolean checkPermission) throws ClassNotFoundException {
+ if (checkPermission) {
+ try {
+ framework.checkAdminPermission(this, AdminPermission.CLASS);
+ } catch (SecurityException e) {
+ throw new ClassNotFoundException(name, e);
+ }
+ }
+ BundleLoader loader = checkLoader();
+ if (loader == null)
+ throw new ClassNotFoundException(NLS.bind(Msg.BUNDLE_CNFE_NOT_RESOLVED, name, getBundleData().getLocation()));
+ try {
+ return (loader.loadClass(name));
+ } catch (ClassNotFoundException e) {
+ // this is to support backward compatibility in eclipse
+ // we always attempted to start a bundle even if the class was not found
+ if (!(e instanceof StatusException) && (bundledata.getStatus() & Constants.BUNDLE_LAZY_START) != 0 && !testStateChanging(Thread.currentThread()))
+ try {
+ // only start the bundle if this is a simple CNFE
+ loader.setLazyTrigger();
+ } catch (BundleException be) {
+ framework.adaptor.getFrameworkLog().log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.WARNING, 0, be.getMessage(), 0, be, null));
+ }
+ throw e;
+ }
+ }
+
+ /**
+ * Find the specified resource in this bundle.
+ *
+ * This bundle's class loader is called to search for the named resource.
+ * If this bundle's state is <tt>INSTALLED</tt>, then only this bundle will
+ * be searched for the specified resource. Imported packages cannot be searched
+ * when a bundle has not been resolved.
+ *
+ * @param name The name of the resource.
+ * See <tt>java.lang.ClassLoader.getResource</tt> for a description of
+ * the format of a resource name.
+ * @return a URL to the named resource, or <tt>null</tt> if the resource could
+ * not be found or if the caller does not have
+ * the <tt>AdminPermission</tt>, and the Java Runtime Environment supports permissions.
+ *
+ * @exception java.lang.IllegalStateException If this bundle has been uninstalled.
+ */
+ public URL getResource(String name) {
+ BundleLoader loader = null;
+ try {
+ framework.checkAdminPermission(this, AdminPermission.RESOURCE);
+ } catch (SecurityException ee) {
+ return null;
+ }
+ loader = checkLoader();
+ if (loader == null) {
+ Enumeration<URL> result = bundledata.findLocalResources(name);
+ if (result != null && result.hasMoreElements())
+ return result.nextElement();
+ return null;
+ }
+ return loader.findResource(name);
+ }
+
+ public Enumeration<URL> getResources(String name) throws IOException {
+ BundleLoader loader = null;
+ try {
+ framework.checkAdminPermission(this, AdminPermission.RESOURCE);
+ } catch (SecurityException ee) {
+ return null;
+ }
+ Enumeration<URL> result;
+ loader = checkLoader();
+ if (loader == null)
+ result = bundledata.findLocalResources(name);
+ else
+ result = loader.getResources(name);
+ if (result != null && result.hasMoreElements())
+ return result;
+ return null;
+ }
+
+ /**
+ * Internal worker to start a bundle.
+ *
+ * @param options the start options
+ */
+ protected void startWorker(int options) throws BundleException {
+ if ((options & START_TRANSIENT) == 0) {
+ setStatus(Constants.BUNDLE_STARTED, true);
+ setStatus(Constants.BUNDLE_ACTIVATION_POLICY, (options & START_ACTIVATION_POLICY) != 0);
+ if (Debug.MONITOR_ACTIVATION)
+ new Exception("A persistent start has been called on bundle: " + getBundleData()).printStackTrace(); //$NON-NLS-1$
+ }
+ if (!framework.active || (state & ACTIVE) != 0)
+ return;
+ if (getInternalStartLevel() > framework.startLevelManager.getStartLevel()) {
+ if ((options & LAZY_TRIGGER) == 0 && (options & START_TRANSIENT) != 0) {
+ // throw exception if this is a transient start
+ String msg = NLS.bind(Msg.BUNDLE_TRANSIENT_START_ERROR, this);
+ // Use a StatusException to indicate to the lazy starter that this should result in a warning
+ throw new BundleException(msg, BundleException.INVALID_OPERATION, new BundleStatusException(msg, StatusException.CODE_WARNING, this));
+ }
+ return;
+ }
+
+ if (state == INSTALLED) {
+ try {
+ if (!framework.packageAdmin.resolveBundles(new Bundle[] {this}, true))
+ throw getResolutionFailureException();
+ } catch (IllegalStateException e) {
+ // Can happen if the resolver detects a nested resolve process
+ throw new BundleException("Unexpected resolution exception.", BundleException.RESOLVE_ERROR, e); //$NON-NLS-1$
+ } catch (ResolverHookException e) {
+ throw new BundleException("Unexpected resolution exception.", BundleException.REJECTED_BY_HOOK, e.getCause()); //$NON-NLS-1$
+ }
+
+ }
+
+ if ((options & START_ACTIVATION_POLICY) != 0 && (bundledata.getStatus() & Constants.BUNDLE_LAZY_START) != 0) {
+ // the bundle must use the activation policy here.
+ if ((state & RESOLVED) != 0) {
+ // now we must publish the LAZY_ACTIVATION event and return
+ state = STARTING;
+ // release the state change lock before sending lazy activation event (bug 258659)
+ completeStateChange();
+ framework.publishBundleEvent(BundleEvent.LAZY_ACTIVATION, this);
+ }
+ return;
+ }
+
+ if (Debug.DEBUG_GENERAL) {
+ Debug.println("Bundle: Active sl = " + framework.startLevelManager.getStartLevel() + "; Bundle " + getBundleId() + " sl = " + getInternalStartLevel()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ if ((options & LAZY_TRIGGER) != 0) {
+ if ((state & RESOLVED) != 0) {
+ // Should publish the lazy activation event here before the starting event
+ // This can happen if another bundle in the same start-level causes a class load from the lazy start bundle.
+ state = STARTING;
+ // release the state change lock before sending lazy activation event (bug 258659)
+ completeStateChange();
+ framework.publishBundleEvent(BundleEvent.LAZY_ACTIVATION, this);
+ beginStateChange();
+ if (state != STARTING) {
+ // while firing the LAZY_ACTIVATION event some one else caused the bundle to transition
+ // out of STARTING. This could have happened because some listener called start on the bundle
+ // or another class load could have caused the start trigger to get fired again.
+ return;
+ }
+ }
+ }
+ state = STARTING;
+ framework.publishBundleEvent(BundleEvent.STARTING, this);
+ context = getContext();
+ //STARTUP TIMING Start here
+ long start = 0;
+
+ BundleWatcher bundleStats = framework.adaptor.getBundleWatcher();
+ if (bundleStats != null)
+ bundleStats.watchBundle(this, BundleWatcher.START_ACTIVATION);
+ if (Debug.DEBUG_BUNDLE_TIME) {
+ start = System.currentTimeMillis();
+ System.out.println("Starting " + getSymbolicName()); //$NON-NLS-1$
+ }
+
+ try {
+ context.start();
+ startHook();
+ if (framework.active) {
+ state = ACTIVE;
+
+ if (Debug.DEBUG_GENERAL) {
+ Debug.println("->started " + this); //$NON-NLS-1$
+ }
+ // release the state change lock before sending lazy activation event (bug 258659)
+ completeStateChange();
+ framework.publishBundleEvent(BundleEvent.STARTED, this);
+ }
+
+ } catch (BundleException e) {
+ // we must fire the stopping event
+ state = STOPPING;
+ framework.publishBundleEvent(BundleEvent.STOPPING, this);
+
+ stopHook();
+ context.close();
+ context = null;
+
+ state = RESOLVED;
+ // if this is a lazy start bundle that fails to start then
+ // we must fire the stopped event
+ framework.publishBundleEvent(BundleEvent.STOPPED, this);
+ throw e;
+ } finally {
+ if (bundleStats != null)
+ bundleStats.watchBundle(this, BundleWatcher.END_ACTIVATION);
+ if (Debug.DEBUG_BUNDLE_TIME)
+ System.out.println("End starting " + getSymbolicName() + " " + (System.currentTimeMillis() - start)); //$NON-NLS-1$ //$NON-NLS-2$
+
+ }
+
+ if (state == UNINSTALLED) {
+ context.close();
+ context = null;
+ throw new BundleException(NLS.bind(Msg.BUNDLE_UNINSTALLED_EXCEPTION, getBundleData().getLocation()), BundleException.STATECHANGE_ERROR);
+ }
+ }
+
+ /**
+ * @throws BundleException
+ */
+ protected void startHook() throws BundleException {
+ // do nothing by default
+ }
+
+ protected boolean readyToResume() {
+ // Return false if the bundle is not at the correct start-level
+ if (getInternalStartLevel() > framework.startLevelManager.getStartLevel())
+ return false;
+ int status = bundledata.getStatus();
+ // Return false if the bundle is not persistently marked for start
+ if ((status & Constants.BUNDLE_STARTED) == 0)
+ return false;
+ if ((status & Constants.BUNDLE_ACTIVATION_POLICY) == 0 || (status & Constants.BUNDLE_LAZY_START) == 0 || isLazyTriggerSet())
+ return true;
+ if (!isResolved()) {
+ if (framework.getAdaptor().getState().isResolved() || !framework.packageAdmin.resolveBundles(new Bundle[] {this}))
+ // should never transition from UNRESOLVED -> STARTING
+ return false;
+ }
+ // now we can publish the LAZY_ACTIVATION event
+ state = STARTING;
+ // release the state change lock before sending lazy activation event (bug 258659)
+ completeStateChange();
+ framework.publishBundleEvent(BundleEvent.LAZY_ACTIVATION, this);
+ return false;
+ }
+
+ private synchronized boolean isLazyTriggerSet() {
+ if (proxy == null)
+ return false;
+ BundleLoader loader = proxy.getBasicBundleLoader();
+ return loader != null ? loader.isLazyTriggerSet() : false;
+ }
+
+ /**
+ * Create a BundleContext for this bundle.
+ *
+ * @return BundleContext for this bundle.
+ */
+ protected BundleContextImpl createContext() {
+ return (new BundleContextImpl(this));
+ }
+
+ /**
+ * Return the current context for this bundle.
+ *
+ * @return BundleContext for this bundle.
+ */
+ protected synchronized BundleContextImpl getContext() {
+ if (context == null) {
+ // only create the context if we are starting, active or stopping
+ // this is so that SCR can get the context for lazy-start bundles
+ if ((state & (STARTING | ACTIVE | STOPPING)) != 0)
+ context = createContext();
+ }
+ return (context);
+ }
+
+ /**
+ * Internal worker to stop a bundle.
+ *
+ * @param options the stop options
+ */
+ protected void stopWorker(int options) throws BundleException {
+ if ((options & STOP_TRANSIENT) == 0) {
+ setStatus(Constants.BUNDLE_STARTED, false);
+ setStatus(Constants.BUNDLE_ACTIVATION_POLICY, false);
+ if (Debug.MONITOR_ACTIVATION)
+ new Exception("A persistent start has been called on bundle: " + getBundleData()).printStackTrace(); //$NON-NLS-1$
+ }
+ if (framework.active) {
+ if ((state & (STOPPING | RESOLVED | INSTALLED)) != 0) {
+ return;
+ }
+
+ BundleWatcher bundleStats = framework.adaptor.getBundleWatcher();
+ if (bundleStats != null)
+ bundleStats.watchBundle(this, BundleWatcher.START_DEACTIVATION);
+
+ state = STOPPING;
+ framework.publishBundleEvent(BundleEvent.STOPPING, this);
+ try {
+ // context may be null if a lazy-start bundle is STARTING
+ if (context != null)
+ context.stop();
+ } finally {
+ stopHook();
+ if (context != null) {
+ context.close();
+ context = null;
+ }
+
+ checkValid();
+
+ state = RESOLVED;
+
+ if (Debug.DEBUG_GENERAL) {
+ Debug.println("->stopped " + this); //$NON-NLS-1$
+ }
+
+ framework.publishBundleEvent(BundleEvent.STOPPED, this);
+ if (bundleStats != null)
+ bundleStats.watchBundle(this, BundleWatcher.END_DEACTIVATION);
+
+ }
+ }
+ }
+
+ /**
+ * @throws BundleException
+ */
+ protected void stopHook() throws BundleException {
+ // do nothing
+ }
+
+ /**
+ * Provides a list of {@link ServiceReference}s for the services
+ * registered by this bundle
+ * or <code>null</code> if the bundle has no registered
+ * services.
+ *
+ * <p>The list is valid at the time
+ * of the call to this method, but the framework is a very dynamic
+ * environment and services can be modified or unregistered at anytime.
+ *
+ * @return An array of {@link ServiceReference} or <code>null</code>.
+ * @exception java.lang.IllegalStateException If the
+ * bundle has been uninstalled.
+ * @see ServiceRegistration
+ * @see ServiceReference
+ */
+ public ServiceReference<?>[] getRegisteredServices() {
+ checkValid();
+
+ if (context == null) {
+ return null;
+ }
+
+ return context.getFramework().getServiceRegistry().getRegisteredServices(context);
+ }
+
+ /**
+ * Provides a list of {@link ServiceReference}s for the
+ * services this bundle is using,
+ * or <code>null</code> if the bundle is not using any services.
+ * A bundle is considered to be using a service if the bundle's
+ * use count for the service is greater than zero.
+ *
+ * <p>The list is valid at the time
+ * of the call to this method, but the framework is a very dynamic
+ * environment and services can be modified or unregistered at anytime.
+ *
+ * @return An array of {@link ServiceReference} or <code>null</code>.
+ * @exception java.lang.IllegalStateException If the
+ * bundle has been uninstalled.
+ * @see ServiceReference
+ */
+ public ServiceReference<?>[] getServicesInUse() {
+ checkValid();
+
+ if (context == null) {
+ return null;
+ }
+
+ return context.getFramework().getServiceRegistry().getServicesInUse(context);
+ }
+
+ public BundleFragment[] getFragments() {
+ synchronized (framework.bundles) {
+ if (fragments == null)
+ return null;
+ BundleFragment[] result = new BundleFragment[fragments.length];
+ System.arraycopy(fragments, 0, result, 0, result.length);
+ return result;
+ }
+ }
+
+ /**
+ * Attaches a fragment to this BundleHost. Fragments must be attached to
+ * the host by ID order. If the ClassLoader of the host is already created
+ * then the fragment must be attached to the host ClassLoader
+ * @param fragment The fragment bundle to attach
+ * return true if the fragment successfully attached; false if the fragment
+ * could not be logically inserted at the end of the fragment chain.
+ */
+ protected void attachFragment(BundleFragment fragment) throws BundleException {
+ // do not force the creation of the bundle loader here
+ BundleLoader loader = getLoaderProxy().getBasicBundleLoader();
+ // If the Host ClassLoader exists then we must attach
+ // the fragment to the ClassLoader.
+ if (loader != null)
+ loader.attachFragment(fragment);
+
+ if (fragments == null) {
+ fragments = new BundleFragment[] {fragment};
+ } else {
+ boolean inserted = false;
+ // We must keep our fragments ordered by bundle ID; or
+ // install order.
+ BundleFragment[] newFragments = new BundleFragment[fragments.length + 1];
+ for (int i = 0; i < fragments.length; i++) {
+ if (fragment == fragments[i])
+ return; // this fragment is already attached
+ // need to flush the other attached fragment manifest caches in case the attaching fragment provides translations (bug 339211)
+ fragments[i].manifestLocalization = null;
+ if (!inserted && fragment.getBundleId() < fragments[i].getBundleId()) {
+ // if the loader has already been created
+ // then we cannot attach a fragment into the middle
+ // of the fragment chain.
+ if (loader != null) {
+ throw new BundleException(NLS.bind(Msg.BUNDLE_LOADER_ATTACHMENT_ERROR, fragments[i].getSymbolicName(), getSymbolicName()), BundleException.INVALID_OPERATION);
+ }
+ newFragments[i] = fragment;
+ inserted = true;
+ }
+ newFragments[inserted ? i + 1 : i] = fragments[i];
+ }
+ if (!inserted)
+ newFragments[newFragments.length - 1] = fragment;
+ fragments = newFragments;
+ }
+ // need to flush the manifest cache in case the attaching fragment provides translations
+ manifestLocalization = null;
+ }
+
+ protected BundleLoader getBundleLoader() {
+ BundleLoaderProxy curProxy = getLoaderProxy();
+ return curProxy == null ? null : curProxy.getBundleLoader();
+ }
+
+ public synchronized BundleLoaderProxy getLoaderProxy() {
+ if (proxy != null)
+ return proxy;
+ BundleDescription bundleDescription = getBundleDescription();
+ if (bundleDescription == null)
+ return null;
+ proxy = new BundleLoaderProxy(this, bundleDescription);
+ // Note that BundleLoaderProxy is a BundleReference
+ // this is necessary to ensure the resolver can continue
+ // to provide BundleRevision objects to resolver hooks.
+ bundleDescription.setUserObject(proxy);
+ return proxy;
+ }
+
+ /**
+ * Gets the class loader for the host bundle. This may end up
+ * creating the bundle class loader if it was not already created.
+ * A null value may be returned if the bundle is not resolved.
+ * @return the bundle class loader or null if the bundle is not resolved.
+ */
+ public ClassLoader getClassLoader() {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null)
+ sm.checkPermission(new RuntimePermission("getClassLoader")); //$NON-NLS-1$
+ BundleLoaderProxy curProxy = getLoaderProxy();
+ BundleLoader loader = curProxy == null ? null : curProxy.getBundleLoader();
+ BundleClassLoader bcl = loader == null ? null : loader.createClassLoader();
+ return (bcl instanceof ClassLoader) ? (ClassLoader) bcl : null;
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleRepository.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleRepository.java
new file mode 100644
index 000000000..f10edf828
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleRepository.java
@@ -0,0 +1,196 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Rob Harrop - SpringSource Inc. (bug 247521)
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.internal.core;
+
+import java.util.*;
+import org.eclipse.osgi.framework.util.KeyedHashSet;
+import org.osgi.framework.Version;
+
+/**
+ * The BundleRepository holds all installed Bundle object for the
+ * Framework. The BundleRepository is also used to mark and unmark
+ * bundle dependancies.
+ *
+ * <p>
+ * This class is internally synchronized and supports client locking. Clients
+ * wishing to perform threadsafe composite operations on instances of this
+ * class can synchronize on the instance itself when doing these operations.
+ */
+public final class BundleRepository {
+ /** bundles by install order */
+ private List<AbstractBundle> bundlesByInstallOrder;
+
+ /** bundles keyed by bundle Id */
+ private KeyedHashSet bundlesById;
+
+ /** bundles keyed by SymbolicName */
+ private Map<String, AbstractBundle[]> bundlesBySymbolicName;
+
+ public BundleRepository(int initialCapacity) {
+ synchronized (this) {
+ bundlesByInstallOrder = new ArrayList<AbstractBundle>(initialCapacity);
+ bundlesById = new KeyedHashSet(initialCapacity, true);
+ bundlesBySymbolicName = new HashMap<String, AbstractBundle[]>(initialCapacity);
+ }
+ }
+
+ /**
+ * Gets a list of bundles ordered by install order.
+ * @return List of bundles by install order.
+ */
+ public synchronized List<AbstractBundle> getBundles() {
+ return bundlesByInstallOrder;
+ }
+
+ /**
+ * Gets a bundle by its bundle Id.
+ * @param bundleId
+ * @return a bundle with the specified id or null if one does not exist
+ */
+ public synchronized AbstractBundle getBundle(long bundleId) {
+ Long key = new Long(bundleId);
+ return (AbstractBundle) bundlesById.getByKey(key);
+ }
+
+ public synchronized AbstractBundle[] getBundles(String symbolicName) {
+ if (Constants.SYSTEM_BUNDLE_SYMBOLICNAME.equals(symbolicName))
+ symbolicName = Constants.getInternalSymbolicName();
+ return bundlesBySymbolicName.get(symbolicName);
+ }
+
+ @SuppressWarnings("unchecked")
+ public synchronized List<AbstractBundle> getBundles(String symbolicName, Version version) {
+ AbstractBundle[] bundles = getBundles(symbolicName);
+ List<AbstractBundle> result = null;
+ if (bundles != null) {
+ if (bundles.length > 0) {
+ for (int i = 0; i < bundles.length; i++) {
+ if (bundles[i].getVersion().equals(version)) {
+ if (result == null)
+ result = new ArrayList<AbstractBundle>();
+ result.add(bundles[i]);
+ }
+ }
+ }
+ }
+ return result == null ? Collections.EMPTY_LIST : result;
+ }
+
+ public synchronized void add(AbstractBundle bundle) {
+ bundlesByInstallOrder.add(bundle);
+ bundlesById.add(bundle);
+ addSymbolicName(bundle);
+ }
+
+ private void addSymbolicName(AbstractBundle bundle) {
+ String symbolicName = bundle.getSymbolicName();
+ if (symbolicName == null)
+ return;
+ AbstractBundle[] bundles = bundlesBySymbolicName.get(symbolicName);
+ if (bundles == null) {
+ // making the initial capacity on this 1 since it
+ // should be rare that multiple version exist
+ bundles = new AbstractBundle[1];
+ bundles[0] = bundle;
+ bundlesBySymbolicName.put(symbolicName, bundles);
+ return;
+ }
+
+ List<AbstractBundle> list = new ArrayList<AbstractBundle>(bundles.length + 1);
+ // find place to insert the bundle
+ Version newVersion = bundle.getVersion();
+ boolean added = false;
+ for (int i = 0; i < bundles.length; i++) {
+ AbstractBundle oldBundle = bundles[i];
+ Version oldVersion = oldBundle.getVersion();
+ if (!added && newVersion.compareTo(oldVersion) >= 0) {
+ added = true;
+ list.add(bundle);
+ }
+ list.add(oldBundle);
+ }
+ if (!added) {
+ list.add(bundle);
+ }
+
+ bundles = new AbstractBundle[list.size()];
+ list.toArray(bundles);
+ bundlesBySymbolicName.put(symbolicName, bundles);
+ }
+
+ public synchronized boolean remove(AbstractBundle bundle) {
+ // remove by bundle ID
+ boolean found = bundlesById.remove(bundle);
+ if (!found)
+ return false;
+
+ // remove by install order
+ bundlesByInstallOrder.remove(bundle);
+ // remove by symbolic name
+ String symbolicName = bundle.getSymbolicName();
+ if (symbolicName == null)
+ return true;
+ removeSymbolicName(symbolicName, bundle);
+ return true;
+ }
+
+ private void removeSymbolicName(String symbolicName, AbstractBundle bundle) {
+ AbstractBundle[] bundles = bundlesBySymbolicName.get(symbolicName);
+ if (bundles == null)
+ return;
+
+ // found some bundles with the global name.
+ // remove all references to the specified bundle.
+ int numRemoved = 0;
+ for (int i = 0; i < bundles.length; i++) {
+ if (bundle == bundles[i]) {
+ numRemoved++;
+ bundles[i] = null;
+ }
+ }
+ if (numRemoved > 0) {
+ if (bundles.length - numRemoved <= 0) {
+ // no bundles left in the array remove the array from the hash
+ bundlesBySymbolicName.remove(symbolicName);
+ } else {
+ // create a new array with the null entries removed.
+ AbstractBundle[] newBundles = new AbstractBundle[bundles.length - numRemoved];
+ int indexCnt = 0;
+ for (int i = 0; i < bundles.length; i++) {
+ if (bundles[i] != null) {
+ newBundles[indexCnt] = bundles[i];
+ indexCnt++;
+ }
+ }
+ bundlesBySymbolicName.put(symbolicName, newBundles);
+ }
+ }
+ }
+
+ public synchronized void update(String oldSymbolicName, AbstractBundle bundle) {
+ if (oldSymbolicName != null) {
+ if (!oldSymbolicName.equals(bundle.getSymbolicName())) {
+ removeSymbolicName(oldSymbolicName, bundle);
+ addSymbolicName(bundle);
+ }
+ } else {
+ addSymbolicName(bundle);
+ }
+ }
+
+ public synchronized void removeAllBundles() {
+ bundlesByInstallOrder.clear();
+ bundlesById = new KeyedHashSet();
+ bundlesBySymbolicName.clear();
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleResourceHandler.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleResourceHandler.java
new file mode 100644
index 000000000..68d28e9ca
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleResourceHandler.java
@@ -0,0 +1,304 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.internal.core;
+
+import java.io.IOException;
+import java.net.*;
+import org.eclipse.osgi.baseadaptor.BaseAdaptor;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
+import org.eclipse.osgi.baseadaptor.loader.BaseClassLoader;
+import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor;
+import org.eclipse.osgi.framework.internal.protocol.ProtocolActivator;
+import org.eclipse.osgi.internal.baseadaptor.AdaptorMsg;
+import org.eclipse.osgi.internal.loader.BundleLoader;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.*;
+
+/**
+ * URLStreamHandler the bundleentry and bundleresource protocols.
+ */
+
+public abstract class BundleResourceHandler extends URLStreamHandler implements ProtocolActivator {
+ public static final String SECURITY_CHECKED = "SECURITY_CHECKED"; //$NON-NLS-1$
+ public static final String SECURITY_UNCHECKED = "SECURITY_UNCHECKED"; //$NON-NLS-1$
+ public static final String BID_FWKID_SEPARATOR = ".fwk"; //$NON-NLS-1$
+ private BaseAdaptor adaptor;
+ protected BundleEntry bundleEntry;
+
+ /**
+ * Constructor for a bundle protocol resource URLStreamHandler.
+ */
+ public BundleResourceHandler() {
+ this(null, null);
+ }
+
+ public BundleResourceHandler(BundleEntry bundleEntry, BaseAdaptor adaptor) {
+ this.bundleEntry = bundleEntry;
+ this.adaptor = adaptor;
+ }
+
+ public void start(BundleContext context, FrameworkAdaptor baseAdaptor) {
+ this.adaptor = (BaseAdaptor) baseAdaptor;
+ }
+
+ /**
+ * Parse reference URL.
+ */
+ protected void parseURL(URL url, String str, int start, int end) {
+ if (end < start)
+ return;
+ if (url.getPath() != null)
+ // A call to a URL constructor has been made that uses an authorized URL as its context.
+ // Null out bundleEntry because it will not be valid for the new path
+ bundleEntry = null;
+ String spec = ""; //$NON-NLS-1$
+ if (start < end)
+ spec = str.substring(start, end);
+ end -= start;
+ //Default is to use path and bundleId from context
+ String path = url.getPath();
+ String host = url.getHost();
+ int resIndex = url.getPort();
+ if (resIndex < 0) // -1 indicates port was not set; must default to 0
+ resIndex = 0;
+ int pathIdx = 0;
+ if (spec.startsWith("//")) { //$NON-NLS-1$
+ int bundleIdIdx = 2;
+ pathIdx = spec.indexOf('/', bundleIdIdx);
+ if (pathIdx == -1) {
+ pathIdx = end;
+ // Use default
+ path = ""; //$NON-NLS-1$
+ }
+ int bundleIdEnd = spec.indexOf(':', bundleIdIdx);
+ if (bundleIdEnd > pathIdx || bundleIdEnd == -1)
+ bundleIdEnd = pathIdx;
+ if (bundleIdEnd < pathIdx - 1)
+ try {
+ resIndex = Integer.parseInt(spec.substring(bundleIdEnd + 1, pathIdx));
+ } catch (NumberFormatException e) {
+ // do nothing; results in resIndex == 0
+ }
+ host = spec.substring(bundleIdIdx, bundleIdEnd);
+ }
+ if (pathIdx < end && spec.charAt(pathIdx) == '/')
+ path = spec.substring(pathIdx, end);
+ else if (end > pathIdx) {
+ if (path == null || path.equals("")) //$NON-NLS-1$
+ path = "/"; //$NON-NLS-1$
+ int last = path.lastIndexOf('/') + 1;
+ if (last == 0)
+ path = spec.substring(pathIdx, end);
+ else
+ path = path.substring(0, last) + spec.substring(pathIdx, end);
+ }
+ if (path == null)
+ path = ""; //$NON-NLS-1$
+ //modify path if there's any relative references
+ // see RFC2396 Section 5.2
+ // Note: For ".." references above the root the approach taken is removing them from the resolved path
+ if (path.endsWith("/.") || path.endsWith("/..")) //$NON-NLS-1$ //$NON-NLS-2$
+ path = path + '/';
+ int dotIndex;
+ while ((dotIndex = path.indexOf("/./")) >= 0) //$NON-NLS-1$
+ path = path.substring(0, dotIndex + 1) + path.substring(dotIndex + 3);
+ while ((dotIndex = path.indexOf("/../")) >= 0) { //$NON-NLS-1$
+ if (dotIndex != 0)
+ path = path.substring(0, path.lastIndexOf('/', dotIndex - 1)) + path.substring(dotIndex + 3);
+ else
+ path = path.substring(dotIndex + 3);
+ }
+ while ((dotIndex = path.indexOf("//")) >= 0) //$NON-NLS-1$
+ path = path.substring(0, dotIndex + 1) + path.substring(dotIndex + 2);
+
+ // Check the permission of the caller to see if they
+ // are allowed access to the resource.
+ String authorized = SECURITY_UNCHECKED;
+ long bundleId = getBundleID(host);
+ Bundle bundle = adaptor == null ? null : adaptor.getBundle(bundleId);
+ if (checkAuthorization(bundle))
+ authorized = SECURITY_CHECKED;
+ // Always force the use of the hash from the adaptor
+ if (adaptor != null)
+ host = Long.toString(bundleId) + BID_FWKID_SEPARATOR + Integer.toString(adaptor.hashCode());
+ // Setting the authority portion of the URL to SECURITY_ATHORIZED
+ // ensures that this URL was created by using this parseURL
+ // method. The openConnection method will only open URLs
+ // that have the authority set to this.
+ setURL(url, url.getProtocol(), host, resIndex, authorized, null, path, null, url.getRef());
+ }
+
+ /**
+ * Establishes a connection to the resource specified by <code>URL</code>.
+ * Since different protocols may have unique ways of connecting, it must be
+ * overridden by the subclass.
+ *
+ * @return java.net.URLConnection
+ * @param url java.net.URL
+ *
+ * @exception IOException thrown if an IO error occurs during connection establishment
+ */
+ protected URLConnection openConnection(URL url) throws IOException {
+ if (bundleEntry != null) // if the bundleEntry is not null then return quick
+ return (new BundleURLConnection(url, bundleEntry));
+
+ String host = url.getHost();
+ if (host == null) {
+ throw new IOException(NLS.bind(AdaptorMsg.URL_NO_BUNDLE_ID, url.toExternalForm()));
+ }
+ AbstractBundle bundle = null;
+ long bundleID;
+ try {
+ bundleID = getBundleID(host);
+ } catch (NumberFormatException nfe) {
+ throw (MalformedURLException) new MalformedURLException(NLS.bind(AdaptorMsg.URL_INVALID_BUNDLE_ID, host)).initCause(nfe);
+ }
+ bundle = adaptor == null ? null : (AbstractBundle) adaptor.getBundle(bundleID);
+ if (bundle == null)
+ throw new IOException(NLS.bind(AdaptorMsg.URL_NO_BUNDLE_FOUND, url.toExternalForm()));
+ // check to make sure that this URL was created using the
+ // parseURL method. This ensures the security check was done
+ // at URL construction.
+ if (!url.getAuthority().equals(SECURITY_CHECKED)) {
+ // No admin security check was made better check now.
+ checkAuthorization(bundle);
+ }
+ return (new BundleURLConnection(url, findBundleEntry(url, bundle)));
+ }
+
+ /**
+ * Finds the bundle entry for this protocal. This is handled
+ * differently for Bundle.gerResource() and Bundle.getEntry()
+ * because getResource uses the bundle classloader and getEntry
+ * only used the base bundle file.
+ * @param url The URL to find the BundleEntry for.
+ * @return the bundle entry
+ */
+ abstract protected BundleEntry findBundleEntry(URL url, AbstractBundle bundle) throws IOException;
+
+ /**
+ * Converts a bundle URL to a String.
+ *
+ * @param url the URL.
+ * @return a string representation of the URL.
+ */
+ protected String toExternalForm(URL url) {
+ StringBuffer result = new StringBuffer(url.getProtocol());
+ result.append("://"); //$NON-NLS-1$
+
+ String host = url.getHost();
+ if ((host != null) && (host.length() > 0))
+ result.append(host);
+ int index = url.getPort();
+ if (index > 0)
+ result.append(':').append(index);
+
+ String path = url.getPath();
+ if (path != null) {
+ if ((path.length() > 0) && (path.charAt(0) != '/')) /* if name doesn't have a leading slash */
+ {
+ result.append("/"); //$NON-NLS-1$
+ }
+
+ result.append(path);
+ }
+ String ref = url.getRef();
+ if (ref != null && ref.length() > 0)
+ result.append('#').append(ref);
+
+ return (result.toString());
+ }
+
+ protected int hashCode(URL url) {
+ int hash = 0;
+ String protocol = url.getProtocol();
+ if (protocol != null)
+ hash += protocol.hashCode();
+
+ String host = url.getHost();
+ if (host != null)
+ hash += host.hashCode();
+
+ hash += url.getPort();
+
+ String path = url.getPath();
+ if (path != null)
+ hash += path.hashCode();
+
+ if (adaptor != null)
+ hash += adaptor.hashCode();
+ return hash;
+ }
+
+ protected boolean equals(URL url1, URL url2) {
+ return sameFile(url1, url2);
+ }
+
+ protected synchronized InetAddress getHostAddress(URL url) {
+ return null;
+ }
+
+ protected boolean hostsEqual(URL url1, URL url2) {
+ String host1 = url1.getHost();
+ String host2 = url2.getHost();
+ if (host1 != null && host2 != null)
+ return host1.equalsIgnoreCase(host2);
+ return (host1 == null && host2 == null);
+ }
+
+ protected boolean sameFile(URL url1, URL url2) {
+ // do a hashcode test to allow each handler to check the adaptor first
+ if (url1.hashCode() != url2.hashCode())
+ return false;
+ String p1 = url1.getProtocol();
+ String p2 = url2.getProtocol();
+ if (!((p1 == p2) || (p1 != null && p1.equalsIgnoreCase(p2))))
+ return false;
+
+ if (!hostsEqual(url1, url2))
+ return false;
+
+ if (url1.getPort() != url2.getPort())
+ return false;
+
+ String path1 = url1.getPath();
+ String path2 = url2.getPath();
+ if (!((path1 == path2) || (path1 != null && path1.equals(path2))))
+ return false;
+
+ return true;
+ // note that the authority is not checked here because it can be different for two
+ // URLs depending on how they were constructed.
+ }
+
+ protected boolean checkAuthorization(Bundle bundle) {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm == null)
+ return true;
+ if (bundle == null)
+ return false;
+ sm.checkPermission(new AdminPermission(bundle, AdminPermission.RESOURCE));
+ return true;
+ }
+
+ protected static BaseClassLoader getBundleClassLoader(AbstractBundle bundle) {
+ BundleLoader loader = bundle.getBundleLoader();
+ if (loader == null)
+ return null;
+ return (BaseClassLoader) loader.createClassLoader();
+ }
+
+ private long getBundleID(String host) {
+ int dotIndex = host.indexOf('.');
+ return (dotIndex >= 0 && dotIndex < host.length() - 1) ? Long.parseLong(host.substring(0, dotIndex)) : Long.parseLong(host);
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleSource.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleSource.java
new file mode 100644
index 000000000..a27e1ee9a
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleSource.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.internal.core;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URLConnection;
+
+/**
+ * BundleSource class to wrap in InputStream.
+ *
+ * <p>This class implements a URLConnection which
+ * wraps an InputStream.
+ */
+public class BundleSource extends URLConnection {
+ private InputStream in;
+
+ protected BundleSource(InputStream in) {
+ super(null);
+ this.in = in;
+ }
+
+ /**
+ * @throws IOException
+ */
+ public void connect() throws IOException {
+ connected = true;
+ }
+
+ /**
+ * @throws IOException
+ */
+ public InputStream getInputStream() throws IOException {
+ return (in);
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleURLConnection.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleURLConnection.java
new file mode 100644
index 000000000..c0163c4cc
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleURLConnection.java
@@ -0,0 +1,131 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.internal.core;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
+import org.eclipse.osgi.internal.baseadaptor.AdaptorMsg;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * URLConnection for BundleClassLoader resources.
+ */
+
+public class BundleURLConnection extends URLConnection {
+ /** BundleEntry that the URL is associated. */
+ protected final BundleEntry bundleEntry;
+
+ /** InputStream for this URLConnection. */
+ protected InputStream in;
+
+ /** content type for this URLConnection */
+ protected String contentType;
+
+ /**
+ * Constructor for a BundleClassLoader resource URLConnection.
+ *
+ * @param url URL for this URLConnection.
+ * @param bundleEntry BundleEntry that the URLConnection is associated.
+ */
+ public BundleURLConnection(URL url, BundleEntry bundleEntry) {
+ super(url);
+
+ this.bundleEntry = bundleEntry;
+ this.in = null;
+ this.contentType = null;
+ }
+
+ public synchronized void connect() throws IOException {
+ if (!connected) {
+ if (bundleEntry != null) {
+ in = bundleEntry.getInputStream();
+ connected = true;
+ } else {
+ throw new IOException(NLS.bind(AdaptorMsg.RESOURCE_NOT_FOUND_EXCEPTION, url));
+ }
+ }
+ }
+
+ public int getContentLength() {
+ return ((int) bundleEntry.getSize());
+ }
+
+ public String getContentType() {
+ if (contentType == null) {
+ contentType = guessContentTypeFromName(bundleEntry.getName());
+
+ if (contentType == null) {
+ if (!connected) {
+ try {
+ connect();
+ } catch (IOException e) {
+ return (null);
+ }
+ }
+ try {
+ if (in.markSupported())
+ contentType = guessContentTypeFromStream(in);
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+ }
+
+ return (contentType);
+ }
+
+ public boolean getDoInput() {
+ return (true);
+ }
+
+ public boolean getDoOutput() {
+ return (false);
+ }
+
+ public InputStream getInputStream() throws IOException {
+ if (!connected) {
+ connect();
+ }
+
+ return (in);
+ }
+
+ public long getLastModified() {
+ long lastModified = bundleEntry.getTime();
+
+ if (lastModified == -1) {
+ return (0);
+ }
+
+ return (lastModified);
+ }
+
+ /**
+ * Converts the URL to a common local URL protocol (i.e file: or jar: protocol)
+ * @return the local URL using a common local protocol
+ */
+ public URL getLocalURL() {
+ return bundleEntry.getLocalURL();
+ }
+
+ /**
+ * Converts the URL to a URL that uses the file: protocol. The content of this
+ * URL may be downloaded or extracted onto the local filesystem to create a file URL.
+ * @return the local URL that uses the file: protocol
+ */
+ public URL getFileURL() {
+ return bundleEntry.getFileURL();
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/ConsoleManager.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/ConsoleManager.java
new file mode 100644
index 000000000..14b3f8f3a
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/ConsoleManager.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.framework.internal.core;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+
+public class ConsoleManager {
+
+ public static final String PROP_CONSOLE = "osgi.console"; //$NON-NLS-1$
+ private static final String PROP_SYSTEM_IN_OUT = "console.systemInOut"; //$NON-NLS-1$
+ private static final String CONSOLE_NAME = "OSGi Console"; //$NON-NLS-1$
+ public static final String CONSOLE_BUNDLE = "org.eclipse.equinox.console"; //$NON-NLS-1$
+ public static final String PROP_CONSOLE_ENABLED = "osgi.console.enable.builtin"; //$NON-NLS-1$
+
+ private final Framework framework;
+ private final String consoleBundle;
+ private final String consolePort;
+
+ public ConsoleManager(Framework framework, String consolePropValue) {
+ String port = null;
+ if (consolePropValue != null) {
+ int index = consolePropValue.lastIndexOf(":"); //$NON-NLS-1$
+ port = consolePropValue.substring(index + 1);
+ }
+ this.consolePort = port != null ? port.trim() : port;
+ String enabled = FrameworkProperties.getProperty(PROP_CONSOLE_ENABLED, CONSOLE_BUNDLE);
+ this.framework = framework;
+ if (!"true".equals(enabled) || "none".equals(consolePort)) { //$NON-NLS-1$ //$NON-NLS-2$
+ this.consoleBundle = "false".equals(enabled) ? CONSOLE_BUNDLE : enabled; //$NON-NLS-1$
+ if (consolePort == null || consolePort.length() > 0) {
+ // no -console was specified or it has specified none or a port for telnet;
+ // need to make sure the gogo shell does not create an interactive console on standard in/out
+ FrameworkProperties.setProperty("gosh.args", "--nointeractive"); //$NON-NLS-1$//$NON-NLS-2$
+ } else {
+ // Need to make sure we don't shutdown the framework if no console is around (bug 362412)
+ FrameworkProperties.setProperty("gosh.args", "--noshutdown"); //$NON-NLS-1$//$NON-NLS-2$
+ }
+ return;
+ }
+ this.consoleBundle = "unknown"; //$NON-NLS-1$
+ }
+
+ public static ConsoleManager startConsole(Framework framework) {
+ ConsoleManager consoleManager = new ConsoleManager(framework, FrameworkProperties.getProperty(PROP_CONSOLE));
+ return consoleManager;
+ }
+
+ public void checkForConsoleBundle() throws BundleException {
+ if ("none".equals(consolePort)) //$NON-NLS-1$
+ return;
+ // otherwise we need to check for the equinox console bundle and start it
+ Bundle[] consoles = framework.getBundleBySymbolicName(consoleBundle);
+ if (consoles == null || consoles.length == 0) {
+ if (consolePort != null)
+ throw new BundleException("Could not find bundle: " + consoleBundle, BundleException.UNSUPPORTED_OPERATION); //$NON-NLS-1$
+ return;
+ }
+ try {
+ consoles[0].start(Bundle.START_TRANSIENT);
+ } catch (BundleException e) {
+ throw new BundleException("Could not start bundle: " + consoleBundle, BundleException.UNSUPPORTED_OPERATION, e); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Stops the OSGi Command console
+ *
+ */
+ public void stopConsole() {
+ // nothing
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/Constants.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/Constants.java
new file mode 100644
index 000000000..1bd279453
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/Constants.java
@@ -0,0 +1,254 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.internal.core;
+
+/**
+ * This interface contains the constants used by the eclipse
+ * OSGi implementation.
+ */
+
+public class Constants implements org.osgi.framework.Constants {
+ /** Default framework version */
+ public static final String OSGI_FRAMEWORK_VERSION = "1.3"; //$NON-NLS-1$
+
+ /** Framework vendor */
+ public static final String OSGI_FRAMEWORK_VENDOR = "Eclipse"; //$NON-NLS-1$
+
+ /** Bundle manifest name */
+ public static final String OSGI_BUNDLE_MANIFEST = "META-INF/MANIFEST.MF"; //$NON-NLS-1$
+
+ /** OSGi framework package name. */
+ public static final String OSGI_FRAMEWORK_PACKAGE = "org.osgi.framework"; //$NON-NLS-1$
+
+ /** Bundle resource URL protocol */
+ public static final String OSGI_RESOURCE_URL_PROTOCOL = "bundleresource"; //$NON-NLS-1$
+
+ /** Bundle entry URL protocol */
+ public static final String OSGI_ENTRY_URL_PROTOCOL = "bundleentry"; //$NON-NLS-1$
+
+ /** Processor aliases resource */
+ public static final String OSGI_PROCESSOR_ALIASES = "processor.aliases"; //$NON-NLS-1$
+
+ /** OS name aliases resource */
+ public static final String OSGI_OSNAME_ALIASES = "osname.aliases"; //$NON-NLS-1$
+
+ /** Default permissions for bundles with no permission set
+ * and there are no default permissions set.
+ */
+ public static final String OSGI_DEFAULT_DEFAULT_PERMISSIONS = "default.permissions"; //$NON-NLS-1$
+
+ /** Base implied permissions for all bundles */
+ public static final String OSGI_BASE_IMPLIED_PERMISSIONS = "implied.permissions"; //$NON-NLS-1$
+
+ /** Name of OSGi LogService */
+ public static final String OSGI_LOGSERVICE_NAME = "org.osgi.service.log.LogService"; //$NON-NLS-1$
+
+ /** Name of OSGi PackageAdmin */
+ public static final String OSGI_PACKAGEADMIN_NAME = "org.osgi.service.packageadmin.PackageAdmin"; //$NON-NLS-1$
+
+ /** Name of OSGi PermissionAdmin */
+ public static final String OSGI_PERMISSIONADMIN_NAME = "org.osgi.service.permissionadmin.PermissionAdmin"; //$NON-NLS-1$
+
+ /** Name of OSGi StartLevel */
+ public static final String OSGI_STARTLEVEL_NAME = "org.osgi.service.startlevel.StartLevel"; //$NON-NLS-1$
+
+ /** JVM java.vm.name property name */
+ public static final String JVM_VM_NAME = "java.vm.name"; //$NON-NLS-1$
+
+ /** JVM os.arch property name */
+ public static final String JVM_OS_ARCH = "os.arch"; //$NON-NLS-1$
+
+ /** JVM os.name property name */
+ public static final String JVM_OS_NAME = "os.name"; //$NON-NLS-1$
+
+ /** JVM os.version property name */
+ public static final String JVM_OS_VERSION = "os.version"; //$NON-NLS-1$
+
+ /** JVM user.language property name */
+ public static final String JVM_USER_LANGUAGE = "user.language"; //$NON-NLS-1$
+
+ /** JVM user.region property name */
+ public static final String JVM_USER_REGION = "user.region"; //$NON-NLS-1$
+
+ /** J2ME configuration property name */
+ public static final String J2ME_MICROEDITION_CONFIGURATION = "microedition.configuration"; //$NON-NLS-1$
+
+ /** J2ME profile property name */
+ public static final String J2ME_MICROEDITION_PROFILES = "microedition.profiles"; //$NON-NLS-1$
+
+ /** Persistent start bundle status */
+ public static final int BUNDLE_STARTED = 0x00000001;
+ /** Lazy start flag bundle status */
+ public static final int BUNDLE_LAZY_START = 0x00000002;
+ public static final int BUNDLE_ACTIVATION_POLICY = 0x00000004;
+
+ /** Property file locations and default names. */
+ public static final String OSGI_PROPERTIES = "osgi.framework.properties"; //$NON-NLS-1$
+ public static final String DEFAULT_OSGI_PROPERTIES = "osgi.properties"; //$NON-NLS-1$
+
+ private static String INTERNAL_SYSTEM_BUNDLE = "org.eclipse.osgi"; //$NON-NLS-1$
+
+ public static String getInternalSymbolicName() {
+ return INTERNAL_SYSTEM_BUNDLE;
+ }
+
+ static void setInternalSymbolicName(String name) {
+ INTERNAL_SYSTEM_BUNDLE = name;
+ }
+
+ /** OSGI implementation version properties key */
+ public static final String OSGI_IMPL_VERSION_KEY = "osgi.framework.version"; //$NON-NLS-1$
+ /** OSGi java profile; used to give a URL to a java profile */
+ public static final String OSGI_JAVA_PROFILE = "osgi.java.profile"; //$NON-NLS-1$
+ public static final String OSGI_JAVA_PROFILE_NAME = "osgi.java.profile.name"; //$NON-NLS-1$
+ /**
+ * OSGi java profile bootdelegation; used to indicate how the org.osgi.framework.bootdelegation
+ * property defined in the java profile should be processed, (ingnore, override, none). default is ignore
+ */
+ public static final String OSGI_JAVA_PROFILE_BOOTDELEGATION = "osgi.java.profile.bootdelegation"; //$NON-NLS-1$
+ /** indicates that the org.osgi.framework.bootdelegation in the java profile should be ingored */
+ public static final String OSGI_BOOTDELEGATION_IGNORE = "ignore"; //$NON-NLS-1$
+ /** indicates that the org.osgi.framework.bootdelegation in the java profile should override the system property */
+ public static final String OSGI_BOOTDELEGATION_OVERRIDE = "override"; //$NON-NLS-1$
+ /** indicates that the org.osgi.framework.bootdelegation in the java profile AND the system properties should be ignored */
+ public static final String OSGI_BOOTDELEGATION_NONE = "none"; //$NON-NLS-1$
+ /** OSGi strict delegation **/
+ public static final String OSGI_RESOLVER_MODE = "osgi.resolverMode"; //$NON-NLS-1$
+ public static final String STRICT_MODE = "strict"; //$NON-NLS-1$
+ public static final String DEVELOPMENT_MODE = "development"; //$NON-NLS-1$
+
+ public static final String STATE_SYSTEM_BUNDLE = "osgi.system.bundle"; //$NON-NLS-1$
+
+ public static final String PROP_OSGI_RELAUNCH = "osgi.framework.relaunch"; //$NON-NLS-1$
+
+ public static String OSGI_COMPATIBILITY_BOOTDELEGATION = "osgi.compatibility.bootdelegation"; //$NON-NLS-1$
+
+ /** Eclipse-SystemBundle header */
+ public static final String ECLIPSE_SYSTEMBUNDLE = "Eclipse-SystemBundle"; //$NON-NLS-1$
+ public static final String ECLIPSE_PLATFORMFILTER = "Eclipse-PlatformFilter"; //$NON-NLS-1$
+ public static final String Eclipse_JREBUNDLE = "Eclipse-JREBundle"; //$NON-NLS-1$
+ /**
+ * Manifest Export-Package directive indicating that the exported package should only
+ * be made available when the resolver is not in strict mode.
+ */
+ public static final String INTERNAL_DIRECTIVE = "x-internal"; //$NON-NLS-1$
+
+ /**
+ * Manifest Export-Package directive indicating that the exported package should only
+ * be made available to friends of the exporting bundle.
+ */
+ public static final String FRIENDS_DIRECTIVE = "x-friends"; //$NON-NLS-1$
+
+ /**
+ * Manifest header (named &quot;Provide-Package&quot;)
+ * identifying the packages name
+ * provided to other bundles which require the bundle.
+ *
+ * <p>
+ * NOTE: this is only used for backwards compatibility, bundles manifest using
+ * syntax version 2 will not recognize this header.
+ *
+ * <p>The attribute value may be retrieved from the
+ * <tt>Dictionary</tt> object returned by the <tt>Bundle.getHeaders</tt> method.
+ * @deprecated
+ */
+ public final static String PROVIDE_PACKAGE = "Provide-Package"; //$NON-NLS-1$
+
+ /**
+ * Manifest header attribute (named &quot;reprovide&quot;)
+ * for Require-Bundle
+ * identifying that any packages that are provided
+ * by the required bundle must be reprovided by the requiring bundle.
+ * The default value is <tt>false</tt>.
+ * <p>
+ * The attribute value is encoded in the Require-Bundle manifest
+ * header like:
+ * <pre>
+ * Require-Bundle: com.acme.module.test; reprovide="true"
+ * </pre>
+ * <p>
+ * NOTE: this is only used for backwards compatibility, bundles manifest using
+ * syntax version 2 will not recognize this attribute.
+ * @deprecated
+ */
+ public final static String REPROVIDE_ATTRIBUTE = "reprovide"; //$NON-NLS-1$
+
+ /**
+ * Manifest header attribute (named &quot;optional&quot;)
+ * for Require-Bundle
+ * identifying that a required bundle is optional and that
+ * the requiring bundle can be resolved if there is no
+ * suitable required bundle.
+ * The default value is <tt>false</tt>.
+ *
+ * <p>The attribute value is encoded in the Require-Bundle manifest
+ * header like:
+ * <pre>
+ * Require-Bundle: com.acme.module.test; optional="true"
+ * </pre>
+ * <p>
+ * NOTE: this is only used for backwards compatibility, bundles manifest using
+ * syntax version 2 will not recognize this attribute.
+ * @since 1.3 <b>EXPERIMENTAL</b>
+ * @deprecated
+ */
+ public final static String OPTIONAL_ATTRIBUTE = "optional"; //$NON-NLS-1$
+
+ /**
+ * The key used to designate the buddy loader associated with a given bundle.
+ */
+ public final static String BUDDY_LOADER = "Eclipse-BuddyPolicy"; //$NON-NLS-1$
+
+ public final static String REGISTERED_POLICY = "Eclipse-RegisterBuddy"; //$NON-NLS-1$
+
+ static public final String INTERNAL_HANDLER_PKGS = "equinox.interal.handler.pkgs"; //$NON-NLS-1$
+
+ // TODO rename it to Eclipse-PluginClass
+ public static final String PLUGIN_CLASS = "Plugin-Class"; //$NON-NLS-1$
+
+ /** Manifest header used to specify the lazy start properties of a bundle */
+ public static final String ECLIPSE_LAZYSTART = "Eclipse-LazyStart"; //$NON-NLS-1$
+
+ /** An Eclipse-LazyStart attribute used to specify exception classes for auto start */
+ public static final String ECLIPSE_LAZYSTART_EXCEPTIONS = "exceptions"; //$NON-NLS-1$
+
+ /**
+ * Manifest header used to specify the auto start properties of a bundle
+ * @deprecated use {@link #ECLIPSE_LAZYSTART}
+ */
+ public static final String ECLIPSE_AUTOSTART = "Eclipse-AutoStart"; //$NON-NLS-1$
+
+ /**
+ * @deprecated use {@link #ECLIPSE_LAZYSTART_EXCEPTIONS}
+ */
+ public static final String ECLIPSE_AUTOSTART_EXCEPTIONS = ECLIPSE_LAZYSTART_EXCEPTIONS;
+
+ /**
+ * Framework launching property specifying whether Equinox's FrameworkWiring
+ * implementation should refresh bundles with equal symbolic names.
+ *
+ * <p>
+ * Default value is <b>TRUE</b> in this release of the Equinox.
+ * This default may change to <b>FALSE</b> in a future Equinox release.
+ * Therefore, code must not assume the default behavior is
+ * <b>TRUE</b> and should interrogate the value of this property to
+ * determine the behavior.
+ *
+ * <p>
+ * The value of this property may be retrieved by calling the
+ * {@code BundleContext.getProperty} method.
+ * @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=351519">bug 351519</a>
+ * @since 3.7.1
+ */
+ public static final String REFRESH_DUPLICATE_BSN = "equinox.refresh.duplicate.bsn"; //$NON-NLS-1$
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/CoreResolverHookFactory.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/CoreResolverHookFactory.java
new file mode 100644
index 000000000..986de041d
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/CoreResolverHookFactory.java
@@ -0,0 +1,216 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.framework.internal.core;
+
+import java.util.*;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.internal.serviceregistry.*;
+import org.eclipse.osgi.service.resolver.ResolverHookException;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.hooks.resolver.ResolverHook;
+import org.osgi.framework.hooks.resolver.ResolverHookFactory;
+import org.osgi.framework.wiring.*;
+
+/**
+ * This class encapsulates the delegation to ResolverHooks that are registered with the service
+ * registry. This way the resolver implementation only has to call out to a single hook
+ * which does all the necessary service registry lookups.
+ *
+ * This class is not thread safe and expects external synchronization.
+ *
+ */
+public class CoreResolverHookFactory implements ResolverHookFactory {
+ // need a tuple to hold the service reference and hook object
+ // do not use a map for performance reasons; no need to hash based on a key.
+ static class HookReference {
+ public HookReference(ServiceReferenceImpl<ResolverHookFactory> reference, ResolverHook hook) {
+ this.reference = reference;
+ this.hook = hook;
+ }
+
+ final ServiceReferenceImpl<ResolverHookFactory> reference;
+ final ResolverHook hook;
+ }
+
+ private final BundleContextImpl context;
+ private final ServiceRegistry registry;
+
+ public CoreResolverHookFactory(BundleContextImpl context, ServiceRegistry registry) {
+ this.context = context;
+ this.registry = registry;
+ }
+
+ void handleHookException(Throwable t, Object hook, String method) {
+ if (Debug.DEBUG_HOOKS) {
+ Debug.println(hook.getClass().getName() + "." + method + "() exception:"); //$NON-NLS-1$ //$NON-NLS-2$
+ if (t != null)
+ Debug.printStackTrace(t);
+ }
+ String message = NLS.bind(Msg.SERVICE_FACTORY_EXCEPTION, hook.getClass().getName(), method);
+ throw new ResolverHookException(message, t);
+ }
+
+ private ServiceReferenceImpl<ResolverHookFactory>[] getHookReferences() {
+ try {
+ @SuppressWarnings("unchecked")
+ ServiceReferenceImpl<ResolverHookFactory>[] result = (ServiceReferenceImpl<ResolverHookFactory>[]) registry.getServiceReferences(context, ResolverHookFactory.class.getName(), null, false, false);
+ return result;
+ } catch (InvalidSyntaxException e) {
+ // cannot happen; no filter
+ return null;
+ }
+ }
+
+ public ResolverHook begin(Collection<BundleRevision> triggers) {
+ if (Debug.DEBUG_HOOKS) {
+ Debug.println("ResolverHook.begin"); //$NON-NLS-1$
+ }
+ ServiceReferenceImpl<ResolverHookFactory>[] refs = getHookReferences();
+ @SuppressWarnings("unchecked")
+ List<HookReference> hookRefs = refs == null ? Collections.EMPTY_LIST : new ArrayList<CoreResolverHookFactory.HookReference>(refs.length);
+ if (refs != null)
+ for (ServiceReferenceImpl<ResolverHookFactory> hookRef : refs) {
+ ResolverHookFactory factory = context.getService(hookRef);
+ if (factory != null) {
+ try {
+ ResolverHook hook = factory.begin(triggers);
+ if (hook != null)
+ hookRefs.add(new HookReference(hookRef, hook));
+ } catch (Throwable t) {
+ // need to force an end call on the ResolverHooks we got and release them
+ try {
+ new CoreResolverHook(hookRefs).end();
+ } catch (Throwable endError) {
+ // we are already in failure mode; just continue
+ }
+ handleHookException(t, factory, "begin"); //$NON-NLS-1$
+ }
+ }
+ }
+ return new CoreResolverHook(hookRefs);
+ }
+
+ void releaseHooks(List<HookReference> hookRefs) {
+ for (HookReference hookRef : hookRefs)
+ context.ungetService(hookRef.reference);
+ hookRefs.clear();
+ }
+
+ class CoreResolverHook implements ResolverHook {
+ private final List<HookReference> hooks;
+
+ CoreResolverHook(List<HookReference> hooks) {
+ this.hooks = hooks;
+ }
+
+ public void filterResolvable(Collection<BundleRevision> candidates) {
+ if (Debug.DEBUG_HOOKS) {
+ Debug.println("ResolverHook.filterResolvable(" + candidates + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ if (hooks.isEmpty())
+ return;
+ candidates = new ShrinkableCollection<BundleRevision>(candidates);
+ for (Iterator<HookReference> iHooks = hooks.iterator(); iHooks.hasNext();) {
+ HookReference hookRef = iHooks.next();
+ if (hookRef.reference.getBundle() == null) {
+ handleHookException(null, hookRef.hook, "filterResolvable"); //$NON-NLS-1$
+ } else {
+ try {
+ hookRef.hook.filterResolvable(candidates);
+ } catch (Throwable t) {
+ handleHookException(t, hookRef.hook, "filterResolvable"); //$NON-NLS-1$
+ }
+ }
+ }
+ }
+
+ public void filterSingletonCollisions(BundleCapability singleton, Collection<BundleCapability> collisionCandidates) {
+ if (Debug.DEBUG_HOOKS) {
+ Debug.println("ResolverHook.filterSingletonCollisions(" + singleton + ", " + collisionCandidates + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ if (hooks.isEmpty())
+ return;
+ collisionCandidates = new ShrinkableCollection<BundleCapability>(collisionCandidates);
+ for (Iterator<HookReference> iHooks = hooks.iterator(); iHooks.hasNext();) {
+ HookReference hookRef = iHooks.next();
+ if (hookRef.reference.getBundle() == null) {
+ handleHookException(null, hookRef.hook, "filterSingletonCollisions"); //$NON-NLS-1$
+ } else {
+ try {
+ hookRef.hook.filterSingletonCollisions(singleton, collisionCandidates);
+ } catch (Throwable t) {
+ handleHookException(t, hookRef.hook, "filterSingletonCollisions"); //$NON-NLS-1$
+ }
+ }
+ }
+ }
+
+ public void filterMatches(BundleRequirement requirement, Collection<BundleCapability> candidates) {
+ if (Debug.DEBUG_HOOKS) {
+ Debug.println("ResolverHook.filterMatches(" + requirement + ", " + candidates + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ if (hooks.isEmpty())
+ return;
+ candidates = new ShrinkableCollection<BundleCapability>(candidates);
+ for (Iterator<HookReference> iHooks = hooks.iterator(); iHooks.hasNext();) {
+ HookReference hookRef = iHooks.next();
+ if (hookRef.reference.getBundle() == null) {
+ handleHookException(null, hookRef.hook, "filterMatches"); //$NON-NLS-1$
+ } else {
+ try {
+ hookRef.hook.filterMatches(requirement, candidates);
+ } catch (Throwable t) {
+ handleHookException(t, hookRef.hook, "filterMatches"); //$NON-NLS-1$
+ }
+ }
+ }
+ }
+
+ public void end() {
+ if (Debug.DEBUG_HOOKS) {
+ Debug.println("ResolverHook.end"); //$NON-NLS-1$
+ }
+ if (hooks.isEmpty())
+ return;
+ try {
+ HookReference missingHook = null;
+ Throwable endError = null;
+ HookReference endBadHook = null;
+ for (Iterator<HookReference> iHooks = hooks.iterator(); iHooks.hasNext();) {
+ HookReference hookRef = iHooks.next();
+ // We do not remove unregistered services here because we are going to remove all of them at the end
+ if (hookRef.reference.getBundle() == null) {
+ if (missingHook == null)
+ missingHook = hookRef;
+ } else {
+ try {
+ hookRef.hook.end();
+ } catch (Throwable t) {
+ // Must continue on to the next hook.end method
+ // save the error for throwing at the end
+ if (endError == null) {
+ endError = t;
+ endBadHook = hookRef;
+ }
+ }
+ }
+ }
+ if (missingHook != null)
+ handleHookException(null, missingHook.hook, "end"); //$NON-NLS-1$
+ if (endError != null)
+ handleHookException(endError, endBadHook.hook, "end"); //$NON-NLS-1$
+ } finally {
+ releaseHooks(hooks);
+ }
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/EquinoxLauncher.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/EquinoxLauncher.java
new file mode 100644
index 000000000..2b71f2467
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/EquinoxLauncher.java
@@ -0,0 +1,347 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.framework.internal.core;
+
+import java.io.*;
+import java.net.URL;
+import java.security.*;
+import java.security.cert.X509Certificate;
+import java.util.*;
+import org.eclipse.core.runtime.adaptor.EclipseStarter;
+import org.eclipse.osgi.baseadaptor.BaseAdaptor;
+import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor;
+import org.osgi.framework.*;
+
+public class EquinoxLauncher implements org.osgi.framework.launch.Framework {
+
+ private volatile Framework framework;
+ private volatile Bundle systemBundle;
+ private final Map<String, String> configuration;
+ private volatile ConsoleManager consoleMgr = null;
+
+ public EquinoxLauncher(Map<String, String> configuration) {
+ this.configuration = configuration;
+ }
+
+ public void init() {
+ checkAdminPermission(AdminPermission.EXECUTE);
+ if (System.getSecurityManager() == null)
+ internalInit();
+ else {
+ AccessController.doPrivileged(new PrivilegedAction<Object>() {
+ public Object run() {
+ internalInit();
+ return null;
+ }
+ });
+ }
+ }
+
+ synchronized Framework internalInit() {
+ if ((getState() & (Bundle.ACTIVE | Bundle.STARTING | Bundle.STOPPING)) != 0)
+ return framework; // no op
+
+ if (System.getSecurityManager() != null && configuration.get(Constants.FRAMEWORK_SECURITY) != null)
+ throw new SecurityException("Cannot specify the \"" + Constants.FRAMEWORK_SECURITY + "\" configuration property when a security manager is already installed."); //$NON-NLS-1$ //$NON-NLS-2$
+
+ Framework current = framework;
+ if (current != null) {
+ current.close();
+ framework = null;
+ systemBundle = null;
+ }
+ ClassLoader tccl = Thread.currentThread().getContextClassLoader();
+ try {
+ FrameworkProperties.setProperties(configuration);
+ FrameworkProperties.initializeProperties();
+ // make sure the active framework thread is used
+ setEquinoxProperties(configuration);
+ current = new Framework(new BaseAdaptor(new String[0]));
+ consoleMgr = ConsoleManager.startConsole(current);
+ current.launch();
+ framework = current;
+ systemBundle = current.systemBundle;
+ } finally {
+ ClassLoader currentCCL = Thread.currentThread().getContextClassLoader();
+ if (currentCCL != tccl)
+ Thread.currentThread().setContextClassLoader(tccl);
+ }
+ return current;
+ }
+
+ private void setEquinoxProperties(Map<String, String> configuration) {
+ Object threadBehavior = configuration == null ? null : configuration.get(Framework.PROP_FRAMEWORK_THREAD);
+ if (threadBehavior == null) {
+ if (FrameworkProperties.getProperty(Framework.PROP_FRAMEWORK_THREAD) == null)
+ FrameworkProperties.setProperty(Framework.PROP_FRAMEWORK_THREAD, Framework.THREAD_NORMAL);
+ } else {
+ FrameworkProperties.setProperty(Framework.PROP_FRAMEWORK_THREAD, (String) threadBehavior);
+ }
+
+ // set the compatibility boot delegation flag to false to get "standard" OSGi behavior WRT boot delegation (bug 344850)
+ if (FrameworkProperties.getProperty(Constants.OSGI_COMPATIBILITY_BOOTDELEGATION) == null)
+ FrameworkProperties.setProperty(Constants.OSGI_COMPATIBILITY_BOOTDELEGATION, "false"); //$NON-NLS-1$
+ // set the support for multiple host to true to get "standard" OSGi behavior (bug 344850)
+ if (FrameworkProperties.getProperty("osgi.support.multipleHosts") == null) //$NON-NLS-1$
+ FrameworkProperties.setProperty("osgi.support.multipleHosts", "true"); //$NON-NLS-1$ //$NON-NLS-2$
+ // first check props we are required to provide reasonable defaults for
+ Object windowSystem = configuration == null ? null : configuration.get(Constants.FRAMEWORK_WINDOWSYSTEM);
+ if (windowSystem == null) {
+ windowSystem = FrameworkProperties.getProperty(EclipseStarter.PROP_WS);
+ if (windowSystem != null)
+ FrameworkProperties.setProperty(Constants.FRAMEWORK_WINDOWSYSTEM, (String) windowSystem);
+ }
+ // rest of props can be ignored if the configuration is null
+ if (configuration == null)
+ return;
+ // check each osgi clean property and set the appropriate equinox one
+ Object clean = configuration.get(Constants.FRAMEWORK_STORAGE_CLEAN);
+ if (Constants.FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT.equals(clean)) {
+ // remove this so we only clean on first init
+ configuration.remove(Constants.FRAMEWORK_STORAGE_CLEAN);
+ FrameworkProperties.setProperty(EclipseStarter.PROP_CLEAN, Boolean.TRUE.toString());
+ }
+ }
+
+ public FrameworkEvent waitForStop(long timeout) throws InterruptedException {
+ Framework current = framework;
+ if (current == null)
+ return new FrameworkEvent(FrameworkEvent.STOPPED, this, null);
+ return current.waitForStop(timeout);
+ }
+
+ public Enumeration<URL> findEntries(String path, String filePattern, boolean recurse) {
+ Bundle current = systemBundle;
+ if (current == null)
+ return null;
+ return current.findEntries(path, filePattern, recurse);
+ }
+
+ public BundleContext getBundleContext() {
+ Bundle current = systemBundle;
+ if (current == null)
+ return null;
+ return current.getBundleContext();
+ }
+
+ public long getBundleId() {
+ return 0;
+ }
+
+ public URL getEntry(String path) {
+ Bundle current = systemBundle;
+ if (current == null)
+ return null;
+ return current.getEntry(path);
+ }
+
+ public Enumeration<String> getEntryPaths(String path) {
+ Bundle current = systemBundle;
+ if (current == null)
+ return null;
+ return current.getEntryPaths(path);
+ }
+
+ public Dictionary<String, String> getHeaders() {
+ Bundle current = systemBundle;
+ if (current == null)
+ return null;
+ return current.getHeaders();
+ }
+
+ public Dictionary<String, String> getHeaders(String locale) {
+ Bundle current = systemBundle;
+ if (current == null)
+ return null;
+ return current.getHeaders(locale);
+ }
+
+ public long getLastModified() {
+ Bundle current = systemBundle;
+ if (current == null)
+ return System.currentTimeMillis();
+ return current.getLastModified();
+ }
+
+ public String getLocation() {
+ return Constants.SYSTEM_BUNDLE_LOCATION;
+ }
+
+ public ServiceReference<?>[] getRegisteredServices() {
+ Bundle current = systemBundle;
+ if (current == null)
+ return null;
+ return current.getRegisteredServices();
+ }
+
+ public URL getResource(String name) {
+ Bundle current = systemBundle;
+ if (current == null)
+ return null;
+ return current.getResource(name);
+ }
+
+ public Enumeration<URL> getResources(String name) throws IOException {
+ Bundle current = systemBundle;
+ if (current == null)
+ return null;
+ return current.getResources(name);
+ }
+
+ public ServiceReference<?>[] getServicesInUse() {
+ Bundle current = systemBundle;
+ if (current == null)
+ return null;
+ return current.getServicesInUse();
+ }
+
+ public int getState() {
+ Bundle current = systemBundle;
+ if (current == null)
+ return Bundle.INSTALLED;
+ return current.getState();
+ }
+
+ public String getSymbolicName() {
+ return FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME;
+ }
+
+ public boolean hasPermission(Object permission) {
+ Bundle current = systemBundle;
+ if (current == null)
+ return false;
+ return current.hasPermission(permission);
+ }
+
+ public Class<?> loadClass(String name) throws ClassNotFoundException {
+ Bundle current = systemBundle;
+ if (current == null)
+ return null;
+ return current.loadClass(name);
+ }
+
+ public void start(int options) throws BundleException {
+ start();
+ }
+
+ /**
+ * @throws BundleException
+ */
+ public void start() throws BundleException {
+ checkAdminPermission(AdminPermission.EXECUTE);
+ if (System.getSecurityManager() == null)
+ internalStart();
+ else
+ try {
+ AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
+ public Object run() {
+ internalStart();
+ return null;
+ }
+ });
+ } catch (PrivilegedActionException e) {
+ throw (BundleException) e.getException();
+ }
+ }
+
+ private void checkAdminPermission(String actions) {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null)
+ sm.checkPermission(new AdminPermission(this, actions));
+ }
+
+ void internalStart() {
+ if (getState() == Bundle.ACTIVE)
+ return;
+ Framework current = internalInit();
+ int level = 1;
+ try {
+ level = Integer.parseInt(configuration.get(Constants.FRAMEWORK_BEGINNING_STARTLEVEL));
+ } catch (Throwable t) {
+ // do nothing
+ }
+ current.startLevelManager.doSetStartLevel(level);
+ }
+
+ public void stop(int options) throws BundleException {
+ stop();
+ }
+
+ public void stop() throws BundleException {
+ Bundle current = systemBundle;
+ if (current == null)
+ return;
+ ConsoleManager currentConsole = consoleMgr;
+ if (currentConsole != null) {
+ currentConsole.stopConsole();
+ consoleMgr = null;
+ }
+ current.stop();
+ }
+
+ public void uninstall() throws BundleException {
+ throw new BundleException(Msg.BUNDLE_SYSTEMBUNDLE_UNINSTALL_EXCEPTION, BundleException.INVALID_OPERATION);
+ }
+
+ public void update() throws BundleException {
+ Bundle current = systemBundle;
+ if (current == null)
+ return;
+ current.update();
+ }
+
+ public void update(InputStream in) throws BundleException {
+ try {
+ in.close();
+ } catch (IOException e) {
+ // nothing; just being nice
+ }
+ update();
+ }
+
+ public Map<X509Certificate, List<X509Certificate>> getSignerCertificates(int signersType) {
+ Bundle current = systemBundle;
+ if (current != null)
+ return current.getSignerCertificates(signersType);
+ @SuppressWarnings("unchecked")
+ final Map<X509Certificate, List<X509Certificate>> empty = Collections.EMPTY_MAP;
+ return empty;
+ }
+
+ public Version getVersion() {
+ Bundle current = systemBundle;
+ if (current != null)
+ return current.getVersion();
+ return Version.emptyVersion;
+ }
+
+ public <A> A adapt(Class<A> adapterType) {
+ Bundle current = systemBundle;
+ if (current != null) {
+ return current.adapt(adapterType);
+ }
+ return null;
+ }
+
+ public int compareTo(Bundle o) {
+ Bundle current = systemBundle;
+ if (current != null)
+ return current.compareTo(o);
+ throw new IllegalStateException();
+ }
+
+ public File getDataFile(String filename) {
+ Bundle current = systemBundle;
+ if (current != null)
+ return current.getDataFile(filename);
+ return null;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/ExportedPackageImpl.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/ExportedPackageImpl.java
new file mode 100644
index 000000000..a26998541
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/ExportedPackageImpl.java
@@ -0,0 +1,99 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.framework.internal.core;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.eclipse.osgi.internal.loader.*;
+import org.eclipse.osgi.service.resolver.BundleDescription;
+import org.eclipse.osgi.service.resolver.ExportPackageDescription;
+import org.osgi.framework.*;
+import org.osgi.framework.Constants;
+import org.osgi.service.packageadmin.ExportedPackage;
+
+/**
+ * @deprecated
+ */
+public class ExportedPackageImpl implements ExportedPackage {
+
+ private final ExportPackageDescription exportedPackage;
+ private final BundleLoaderProxy supplier;
+
+ public ExportedPackageImpl(ExportPackageDescription exportedPackage, BundleLoaderProxy supplier) {
+ this.exportedPackage = exportedPackage;
+ this.supplier = supplier;
+ }
+
+ public String getName() {
+ return exportedPackage.getName();
+ }
+
+ public org.osgi.framework.Bundle getExportingBundle() {
+ if (supplier.isStale())
+ return null;
+ return supplier.getBundleHost();
+ }
+
+ /*
+ * get the bundle without checking if it is stale
+ */
+ AbstractBundle getBundle() {
+ return supplier.getBundleHost();
+ }
+
+ public Bundle[] getImportingBundles() {
+ if (supplier.isStale())
+ return null;
+ AbstractBundle bundle = (AbstractBundle) getExportingBundle();
+ if (bundle == null)
+ return null;
+ AbstractBundle[] bundles = bundle.framework.getAllBundles();
+ List<Bundle> importers = new ArrayList<Bundle>(10);
+ PackageSource supplierSource = supplier.createPackageSource(exportedPackage, false);
+ for (int i = 0; i < bundles.length; i++) {
+ if (!(bundles[i] instanceof BundleHost))
+ continue;
+ BundleLoader loader = ((BundleHost) bundles[i]).getBundleLoader();
+ if (loader == null || loader.getBundle() == supplier.getBundle())
+ continue; // do not include include the exporter of the package
+ PackageSource importerSource = loader.getPackageSource(getName());
+ if (supplierSource != null && supplierSource.hasCommonSource(importerSource))
+ importers.add(bundles[i]);
+ }
+ return importers.toArray(new Bundle[importers.size()]);
+ }
+
+ /**
+ * @deprecated
+ */
+ public String getSpecificationVersion() {
+ return exportedPackage.getVersion().toString();
+ }
+
+ public Version getVersion() {
+ return exportedPackage.getVersion();
+ }
+
+ public boolean isRemovalPending() {
+ BundleDescription exporter = exportedPackage.getExporter();
+ if (exporter != null)
+ return exporter.isRemovalPending();
+ return true;
+ }
+
+ public String toString() {
+ StringBuffer result = new StringBuffer(getName());
+ result.append("; ").append(Constants.VERSION_ATTRIBUTE); //$NON-NLS-1$
+ result.append("=\"").append(exportedPackage.getVersion().toString()).append("\""); //$NON-NLS-1$//$NON-NLS-2$
+
+ return result.toString();
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/FilterImpl.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/FilterImpl.java
new file mode 100644
index 000000000..4611c63cb
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/FilterImpl.java
@@ -0,0 +1,1751 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.internal.core;
+
+import java.lang.reflect.*;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.*;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.framework.util.Headers;
+import org.eclipse.osgi.internal.serviceregistry.ServiceReferenceImpl;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.*;
+
+/**
+ * RFC 1960-based Filter. Filter objects can be created by calling
+ * the constructor with the desired filter string.
+ * A Filter object can be called numerous times to determine if the
+ * match argument matches the filter string that was used to create the Filter
+ * object.
+ *
+ * <p>The syntax of a filter string is the string representation
+ * of LDAP search filters as defined in RFC 1960:
+ * <i>A String Representation of LDAP Search Filters</i> (available at
+ * http://www.ietf.org/rfc/rfc1960.txt).
+ * It should be noted that RFC 2254:
+ * <i>A String Representation of LDAP Search Filters</i>
+ * (available at http://www.ietf.org/rfc/rfc2254.txt) supersedes
+ * RFC 1960 but only adds extensible matching and is not applicable for this
+ * API.
+ *
+ * <p>The string representation of an LDAP search filter is defined by the
+ * following grammar. It uses a prefix format.
+ * <pre>
+ * &lt;filter&gt; ::= '(' &lt;filtercomp&gt; ')'
+ * &lt;filtercomp&gt; ::= &lt;and&gt; | &lt;or&gt; | &lt;not&gt; | &lt;item&gt;
+ * &lt;and&gt; ::= '&' &lt;filterlist&gt;
+ * &lt;or&gt; ::= '|' &lt;filterlist&gt;
+ * &lt;not&gt; ::= '!' &lt;filter&gt;
+ * &lt;filterlist&gt; ::= &lt;filter&gt; | &lt;filter&gt; &lt;filterlist&gt;
+ * &lt;item&gt; ::= &lt;simple&gt; | &lt;present&gt; | &lt;substring&gt;
+ * &lt;simple&gt; ::= &lt;attr&gt; &lt;filtertype&gt; &lt;value&gt;
+ * &lt;filtertype&gt; ::= &lt;equal&gt; | &lt;approx&gt; | &lt;greater&gt; | &lt;less&gt;
+ * &lt;equal&gt; ::= '='
+ * &lt;approx&gt; ::= '~='
+ * &lt;greater&gt; ::= '&gt;='
+ * &lt;less&gt; ::= '&lt;='
+ * &lt;present&gt; ::= &lt;attr&gt; '=*'
+ * &lt;substring&gt; ::= &lt;attr&gt; '=' &lt;initial&gt; &lt;any&gt; &lt;final&gt;
+ * &lt;initial&gt; ::= NULL | &lt;value&gt;
+ * &lt;any&gt; ::= '*' &lt;starval&gt;
+ * &lt;starval&gt; ::= NULL | &lt;value&gt; '*' &lt;starval&gt;
+ * &lt;final&gt; ::= NULL | &lt;value&gt;
+ * </pre>
+ *
+ * <code>&lt;attr&gt;</code> is a string representing an attribute, or
+ * key, in the properties objects of the registered services.
+ * Attribute names are not case sensitive;
+ * that is cn and CN both refer to the same attribute.
+ * <code>&lt;value&gt;</code> is a string representing the value, or part of
+ * one, of a key in the properties objects of the registered services.
+ * If a <code>&lt;value&gt;</code> must
+ * contain one of the characters '<code>*</code>' or '<code>(</code>'
+ * or '<code>)</code>', these characters
+ * should be escaped by preceding them with the backslash '<code>\</code>'
+ * character.
+ * Note that although both the <code>&lt;substring&gt;</code> and
+ * <code>&lt;present&gt;</code> productions can
+ * produce the <code>'attr=*'</code> construct, this construct is used only to
+ * denote a presence filter.
+ *
+ * <p>Examples of LDAP filters are:
+ *
+ * <pre>
+ * &quot;(cn=Babs Jensen)&quot;
+ * &quot;(!(cn=Tim Howes))&quot;
+ * &quot;(&(&quot; + Constants.OBJECTCLASS + &quot;=Person)(|(sn=Jensen)(cn=Babs J*)))&quot;
+ * &quot;(o=univ*of*mich*)&quot;
+ * </pre>
+ *
+ * <p>The approximate match (<code>~=</code>) is implementation specific but
+ * should at least ignore case and white space differences. Optional are
+ * codes like soundex or other smart "closeness" comparisons.
+ *
+ * <p>Comparison of values is not straightforward. Strings
+ * are compared differently than numbers and it is
+ * possible for a key to have multiple values. Note that
+ * that keys in the match argument must always be strings.
+ * The comparison is defined by the object type of the key's
+ * value. The following rules apply for comparison:
+ *
+ * <blockquote>
+ * <TABLE BORDER=0>
+ * <TR><TD><b>Property Value Type </b></TD><TD><b>Comparison Type</b></TD></TR>
+ * <TR><TD>String </TD><TD>String comparison</TD></TR>
+ * <TR valign=top><TD>Integer, Long, Float, Double, Byte, Short, BigInteger, BigDecimal </TD><TD>numerical comparison</TD></TR>
+ * <TR><TD>Character </TD><TD>character comparison</TD></TR>
+ * <TR><TD>Boolean </TD><TD>equality comparisons only</TD></TR>
+ * <TR><TD>[] (array)</TD><TD>recursively applied to values </TD></TR>
+ * <TR><TD>Vector</TD><TD>recursively applied to elements </TD></TR>
+ * </TABLE>
+ * Note: arrays of primitives are also supported.
+ * </blockquote>
+ *
+ * A filter matches a key that has multiple values if it
+ * matches at least one of those values. For example,
+ * <pre>
+ * Dictionary d = new Hashtable();
+ * d.put( "cn", new String[] { "a", "b", "c" } );
+ * </pre>
+ * d will match <code>(cn=a)</code> and also <code>(cn=b)</code>
+ *
+ * <p>A filter component that references a key having an unrecognizable
+ * data type will evaluate to <code>false</code> .
+ */
+
+public class FilterImpl implements Filter /* since Framework 1.1 */{
+ /* public methods in org.osgi.framework.Filter */
+
+ /**
+ * Constructs a {@link FilterImpl} object. This filter object may be used
+ * to match a {@link ServiceReferenceImpl} or a Dictionary.
+ *
+ * <p> If the filter cannot be parsed, an {@link InvalidSyntaxException}
+ * will be thrown with a human readable message where the
+ * filter became unparsable.
+ *
+ * @param filterString the filter string.
+ * @exception InvalidSyntaxException If the filter parameter contains
+ * an invalid filter string that cannot be parsed.
+ */
+ public static FilterImpl newInstance(String filterString) throws InvalidSyntaxException {
+ return new Parser(filterString).parse();
+ }
+
+ /**
+ * Filter using a service's properties.
+ * <p>
+ * This {@code Filter} is executed using the keys and values of the
+ * referenced service's properties. The keys are looked up in a case
+ * insensitive manner.
+ *
+ * @param reference The reference to the service whose properties are used
+ * in the match.
+ * @return {@code true} if the service's properties match this
+ * {@code Filter}; {@code false} otherwise.
+ */
+ public boolean match(ServiceReference<?> reference) {
+ if (reference instanceof ServiceReferenceImpl) {
+ return matchCase(((ServiceReferenceImpl<?>) reference).getRegistration().getProperties());
+ }
+ return matchCase(new ServiceReferenceDictionary(reference));
+ }
+
+ /**
+ * Filter using a {@code Dictionary} with case insensitive key lookup. This
+ * {@code Filter} is executed using the specified {@code Dictionary}'s keys
+ * and values. The keys are looked up in a case insensitive manner.
+ *
+ * @param dictionary The {@code Dictionary} whose key/value pairs are used
+ * in the match.
+ * @return {@code true} if the {@code Dictionary}'s values match this
+ * filter; {@code false} otherwise.
+ * @throws IllegalArgumentException If {@code dictionary} contains case
+ * variants of the same key name.
+ */
+ public boolean match(Dictionary<String, ?> dictionary) {
+ if (dictionary != null) {
+ dictionary = new Headers<String, Object>(dictionary);
+ }
+
+ return matchCase(dictionary);
+ }
+
+ /**
+ * Filter using a {@code Dictionary}. This {@code Filter} is executed using
+ * the specified {@code Dictionary}'s keys and values. The keys are looked
+ * up in a normal manner respecting case.
+ *
+ * @param dictionary The {@code Dictionary} whose key/value pairs are used
+ * in the match.
+ * @return {@code true} if the {@code Dictionary}'s values match this
+ * filter; {@code false} otherwise.
+ * @since 1.3
+ */
+ public boolean matchCase(Dictionary<String, ?> dictionary) {
+ switch (op) {
+ case AND : {
+ FilterImpl[] filters = (FilterImpl[]) value;
+ for (FilterImpl f : filters) {
+ if (!f.matchCase(dictionary)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ case OR : {
+ FilterImpl[] filters = (FilterImpl[]) value;
+ for (FilterImpl f : filters) {
+ if (f.matchCase(dictionary)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ case NOT : {
+ FilterImpl filter = (FilterImpl) value;
+
+ return !filter.matchCase(dictionary);
+ }
+
+ case SUBSTRING :
+ case EQUAL :
+ case GREATER :
+ case LESS :
+ case APPROX : {
+ Object prop = (dictionary == null) ? null : dictionary.get(attr);
+
+ return compare(op, prop, value);
+ }
+
+ case PRESENT : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("PRESENT(" + attr + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ Object prop = (dictionary == null) ? null : dictionary.get(attr);
+
+ return prop != null;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Filter using a {@code Map}. This {@code Filter} is executed using the
+ * specified {@code Map}'s keys and values. The keys are looked up in a
+ * normal manner respecting case.
+ *
+ * @param map The {@code Map} whose key/value pairs are used in the match.
+ * Maps with {@code null} key or values are not supported. A
+ * {@code null} value is considered not present to the filter.
+ * @return {@code true} if the {@code Map}'s values match this filter;
+ * {@code false} otherwise.
+ * @since 1.6
+ */
+ public boolean matches(Map<String, ?> map) {
+ switch (op) {
+ case AND : {
+ FilterImpl[] filters = (FilterImpl[]) value;
+ for (FilterImpl f : filters) {
+ if (!f.matches(map)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ case OR : {
+ FilterImpl[] filters = (FilterImpl[]) value;
+ for (FilterImpl f : filters) {
+ if (f.matches(map)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ case NOT : {
+ FilterImpl filter = (FilterImpl) value;
+
+ return !filter.matches(map);
+ }
+
+ case SUBSTRING :
+ case EQUAL :
+ case GREATER :
+ case LESS :
+ case APPROX : {
+ Object prop = (map == null) ? null : map.get(attr);
+
+ return compare(op, prop, value);
+ }
+
+ case PRESENT : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("PRESENT(" + attr + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ Object prop = (map == null) ? null : map.get(attr);
+
+ return prop != null;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns this <code>Filter</code> object's filter string.
+ * <p>
+ * The filter string is normalized by removing whitespace which does not
+ * affect the meaning of the filter.
+ *
+ * @return Filter string.
+ */
+
+ public String toString() {
+ String result = filterString;
+ if (result == null) {
+ filterString = result = normalize().toString();
+ }
+ return result;
+ }
+
+ /**
+ * Returns this <code>Filter</code>'s normalized filter string.
+ * <p>
+ * The filter string is normalized by removing whitespace which does not
+ * affect the meaning of the filter.
+ *
+ * @return This <code>Filter</code>'s filter string.
+ */
+ private StringBuffer normalize() {
+ StringBuffer sb = new StringBuffer();
+ sb.append('(');
+
+ switch (op) {
+ case AND : {
+ sb.append('&');
+
+ FilterImpl[] filters = (FilterImpl[]) value;
+ for (FilterImpl f : filters) {
+ sb.append(f.normalize());
+ }
+
+ break;
+ }
+
+ case OR : {
+ sb.append('|');
+
+ FilterImpl[] filters = (FilterImpl[]) value;
+ for (FilterImpl f : filters) {
+ sb.append(f.normalize());
+ }
+
+ break;
+ }
+
+ case NOT : {
+ sb.append('!');
+ FilterImpl filter = (FilterImpl) value;
+ sb.append(filter.normalize());
+
+ break;
+ }
+
+ case SUBSTRING : {
+ sb.append(attr);
+ sb.append('=');
+
+ String[] substrings = (String[]) value;
+
+ for (String substr : substrings) {
+ if (substr == null) /* * */{
+ sb.append('*');
+ } else /* xxx */{
+ sb.append(encodeValue(substr));
+ }
+ }
+
+ break;
+ }
+ case EQUAL : {
+ sb.append(attr);
+ sb.append('=');
+ sb.append(encodeValue((String) value));
+
+ break;
+ }
+ case GREATER : {
+ sb.append(attr);
+ sb.append(">="); //$NON-NLS-1$
+ sb.append(encodeValue((String) value));
+
+ break;
+ }
+ case LESS : {
+ sb.append(attr);
+ sb.append("<="); //$NON-NLS-1$
+ sb.append(encodeValue((String) value));
+
+ break;
+ }
+ case APPROX : {
+ sb.append(attr);
+ sb.append("~="); //$NON-NLS-1$
+ sb.append(encodeValue(approxString((String) value)));
+
+ break;
+ }
+
+ case PRESENT : {
+ sb.append(attr);
+ sb.append("=*"); //$NON-NLS-1$
+
+ break;
+ }
+ }
+
+ sb.append(')');
+
+ return sb;
+ }
+
+ /**
+ * Compares this <code>Filter</code> object to another object.
+ *
+ * @param obj The object to compare against this <code>Filter</code>
+ * object.
+ * @return If the other object is a <code>Filter</code> object, then
+ * returns <code>this.toString().equals(obj.toString()</code>;
+ * <code>false</code> otherwise.
+ */
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+
+ if (!(obj instanceof Filter)) {
+ return false;
+ }
+
+ return this.toString().equals(obj.toString());
+ }
+
+ /**
+ * Returns the hashCode for this <code>Filter</code> object.
+ *
+ * @return The hashCode of the filter string; that is,
+ * <code>this.toString().hashCode()</code>.
+ */
+ public int hashCode() {
+ return this.toString().hashCode();
+ }
+
+ /* non public fields and methods for the Filter implementation */
+
+ /** filter operation */
+ private final int op;
+ private static final int EQUAL = 1;
+ private static final int APPROX = 2;
+ private static final int GREATER = 3;
+ private static final int LESS = 4;
+ private static final int PRESENT = 5;
+ private static final int SUBSTRING = 6;
+ private static final int AND = 7;
+ private static final int OR = 8;
+ private static final int NOT = 9;
+
+ /** filter attribute or null if operation AND, OR or NOT */
+ private final String attr;
+ /** filter operands */
+ private final Object value;
+
+ /* normalized filter string for topLevel Filter object */
+ private transient volatile String filterString;
+
+ FilterImpl(int operation, String attr, Object value) {
+ this.op = operation;
+ this.attr = attr;
+ this.value = value;
+ }
+
+ /**
+ * Encode the value string such that '(', '*', ')'
+ * and '\' are escaped.
+ *
+ * @param value unencoded value string.
+ * @return encoded value string.
+ */
+ private static String encodeValue(String value) {
+ boolean encoded = false;
+ int inlen = value.length();
+ int outlen = inlen << 1; /* inlen * 2 */
+
+ char[] output = new char[outlen];
+ value.getChars(0, inlen, output, inlen);
+
+ int cursor = 0;
+ for (int i = inlen; i < outlen; i++) {
+ char c = output[i];
+
+ switch (c) {
+ case '(' :
+ case '*' :
+ case ')' :
+ case '\\' : {
+ output[cursor] = '\\';
+ cursor++;
+ encoded = true;
+
+ break;
+ }
+ }
+
+ output[cursor] = c;
+ cursor++;
+ }
+
+ return encoded ? new String(output, 0, cursor) : value;
+ }
+
+ private boolean compare(int operation, Object value1, Object value2) {
+ if (value1 == null) {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("compare(" + value1 + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ return false;
+ }
+
+ if (value1 instanceof String) {
+ return compare_String(operation, (String) value1, value2);
+ }
+
+ Class<?> clazz = value1.getClass();
+ if (clazz.isArray()) {
+ Class<?> type = clazz.getComponentType();
+ if (type.isPrimitive()) {
+ return compare_PrimitiveArray(operation, type, value1, value2);
+ }
+ return compare_ObjectArray(operation, (Object[]) value1, value2);
+ }
+ if (value1 instanceof Collection<?>) {
+ return compare_Collection(operation, (Collection<?>) value1, value2);
+ }
+
+ if (value1 instanceof Integer) {
+ return compare_Integer(operation, ((Integer) value1).intValue(), value2);
+ }
+
+ if (value1 instanceof Long) {
+ return compare_Long(operation, ((Long) value1).longValue(), value2);
+ }
+
+ if (value1 instanceof Byte) {
+ return compare_Byte(operation, ((Byte) value1).byteValue(), value2);
+ }
+
+ if (value1 instanceof Short) {
+ return compare_Short(operation, ((Short) value1).shortValue(), value2);
+ }
+
+ if (value1 instanceof Character) {
+ return compare_Character(operation, ((Character) value1).charValue(), value2);
+ }
+
+ if (value1 instanceof Float) {
+ return compare_Float(operation, ((Float) value1).floatValue(), value2);
+ }
+
+ if (value1 instanceof Double) {
+ return compare_Double(operation, ((Double) value1).doubleValue(), value2);
+ }
+
+ if (value1 instanceof Boolean) {
+ return compare_Boolean(operation, ((Boolean) value1).booleanValue(), value2);
+ }
+ if (value1 instanceof Comparable<?>) {
+ @SuppressWarnings("unchecked")
+ Comparable<Object> comparable = (Comparable<Object>) value1;
+ return compare_Comparable(operation, comparable, value2);
+ }
+
+ return compare_Unknown(operation, value1, value2); // RFC 59
+ }
+
+ private boolean compare_Collection(int operation, Collection<?> collection, Object value2) {
+ for (Object value1 : collection) {
+ if (compare(operation, value1, value2)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean compare_ObjectArray(int operation, Object[] array, Object value2) {
+ for (Object value1 : array) {
+ if (compare(operation, value1, value2)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean compare_PrimitiveArray(int operation, Class<?> type, Object primarray, Object value2) {
+ if (Integer.TYPE.isAssignableFrom(type)) {
+ int[] array = (int[]) primarray;
+ for (int value1 : array) {
+ if (compare_Integer(operation, value1, value2)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ if (Long.TYPE.isAssignableFrom(type)) {
+ long[] array = (long[]) primarray;
+ for (long value1 : array) {
+ if (compare_Long(operation, value1, value2)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ if (Byte.TYPE.isAssignableFrom(type)) {
+ byte[] array = (byte[]) primarray;
+ for (byte value1 : array) {
+ if (compare_Byte(operation, value1, value2)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ if (Short.TYPE.isAssignableFrom(type)) {
+ short[] array = (short[]) primarray;
+ for (short value1 : array) {
+ if (compare_Short(operation, value1, value2)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ if (Character.TYPE.isAssignableFrom(type)) {
+ char[] array = (char[]) primarray;
+ for (char value1 : array) {
+ if (compare_Character(operation, value1, value2)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ if (Float.TYPE.isAssignableFrom(type)) {
+ float[] array = (float[]) primarray;
+ for (float value1 : array) {
+ if (compare_Float(operation, value1, value2)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ if (Double.TYPE.isAssignableFrom(type)) {
+ double[] array = (double[]) primarray;
+ for (double value1 : array) {
+ if (compare_Double(operation, value1, value2)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ if (Boolean.TYPE.isAssignableFrom(type)) {
+ boolean[] array = (boolean[]) primarray;
+ for (boolean value1 : array) {
+ if (compare_Boolean(operation, value1, value2)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ return false;
+ }
+
+ private boolean compare_String(int operation, String string, Object value2) {
+ switch (operation) {
+ case SUBSTRING : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("SUBSTRING(" + string + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ String[] substrings = (String[]) value2;
+ int pos = 0;
+ for (int i = 0, size = substrings.length; i < size; i++) {
+ String substr = substrings[i];
+
+ if (i + 1 < size) /* if this is not that last substr */{
+ if (substr == null) /* * */{
+ String substr2 = substrings[i + 1];
+
+ if (substr2 == null) /* ** */
+ continue; /* ignore first star */
+ /* *xxx */
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("indexOf(\"" + substr2 + "\"," + pos + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ int index = string.indexOf(substr2, pos);
+ if (index == -1) {
+ return false;
+ }
+
+ pos = index + substr2.length();
+ if (i + 2 < size) // if there are more substrings, increment over the string we just matched; otherwise need to do the last substr check
+ i++;
+ } else /* xxx */{
+ int len = substr.length();
+
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("regionMatches(" + pos + ",\"" + substr + "\")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ if (string.regionMatches(pos, substr, 0, len)) {
+ pos += len;
+ } else {
+ return false;
+ }
+ }
+ } else /* last substr */{
+ if (substr == null) /* * */{
+ return true;
+ }
+ /* xxx */
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("regionMatches(" + pos + "," + substr + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return string.endsWith(substr);
+ }
+ }
+
+ return true;
+ }
+ case EQUAL : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("EQUAL(" + string + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return string.equals(value2);
+ }
+ case APPROX : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("APPROX(" + string + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ string = approxString(string);
+ String string2 = approxString((String) value2);
+
+ return string.equalsIgnoreCase(string2);
+ }
+ case GREATER : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("GREATER(" + string + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return string.compareTo((String) value2) >= 0;
+ }
+ case LESS : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("LESS(" + string + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return string.compareTo((String) value2) <= 0;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean compare_Integer(int operation, int intval, Object value2) {
+ if (operation == SUBSTRING) {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("SUBSTRING(" + intval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return false;
+ }
+
+ int intval2;
+ try {
+ intval2 = Integer.parseInt(((String) value2).trim());
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ switch (operation) {
+ case EQUAL : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("EQUAL(" + intval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return intval == intval2;
+ }
+ case APPROX : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("APPROX(" + intval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return intval == intval2;
+ }
+ case GREATER : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("GREATER(" + intval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return intval >= intval2;
+ }
+ case LESS : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("LESS(" + intval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return intval <= intval2;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean compare_Long(int operation, long longval, Object value2) {
+ if (operation == SUBSTRING) {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("SUBSTRING(" + longval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return false;
+ }
+
+ long longval2;
+ try {
+ longval2 = Long.parseLong(((String) value2).trim());
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ switch (operation) {
+ case EQUAL : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("EQUAL(" + longval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return longval == longval2;
+ }
+ case APPROX : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("APPROX(" + longval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return longval == longval2;
+ }
+ case GREATER : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("GREATER(" + longval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return longval >= longval2;
+ }
+ case LESS : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("LESS(" + longval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return longval <= longval2;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean compare_Byte(int operation, byte byteval, Object value2) {
+ if (operation == SUBSTRING) {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("SUBSTRING(" + byteval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return false;
+ }
+
+ byte byteval2;
+ try {
+ byteval2 = Byte.parseByte(((String) value2).trim());
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ switch (operation) {
+ case EQUAL : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("EQUAL(" + byteval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return byteval == byteval2;
+ }
+ case APPROX : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("APPROX(" + byteval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return byteval == byteval2;
+ }
+ case GREATER : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("GREATER(" + byteval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return byteval >= byteval2;
+ }
+ case LESS : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("LESS(" + byteval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return byteval <= byteval2;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean compare_Short(int operation, short shortval, Object value2) {
+ if (operation == SUBSTRING) {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("SUBSTRING(" + shortval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return false;
+ }
+
+ short shortval2;
+ try {
+ shortval2 = Short.parseShort(((String) value2).trim());
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ switch (operation) {
+ case EQUAL : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("EQUAL(" + shortval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return shortval == shortval2;
+ }
+ case APPROX : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("APPROX(" + shortval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return shortval == shortval2;
+ }
+ case GREATER : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("GREATER(" + shortval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return shortval >= shortval2;
+ }
+ case LESS : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("LESS(" + shortval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return shortval <= shortval2;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean compare_Character(int operation, char charval, Object value2) {
+ if (operation == SUBSTRING) {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("SUBSTRING(" + charval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return false;
+ }
+
+ char charval2;
+ try {
+ charval2 = ((String) value2).charAt(0);
+ } catch (IndexOutOfBoundsException e) {
+ return false;
+ }
+ switch (operation) {
+ case EQUAL : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("EQUAL(" + charval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return charval == charval2;
+ }
+ case APPROX : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("APPROX(" + charval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return (charval == charval2) || (Character.toUpperCase(charval) == Character.toUpperCase(charval2)) || (Character.toLowerCase(charval) == Character.toLowerCase(charval2));
+ }
+ case GREATER : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("GREATER(" + charval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return charval >= charval2;
+ }
+ case LESS : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("LESS(" + charval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return charval <= charval2;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean compare_Boolean(int operation, boolean boolval, Object value2) {
+ if (operation == SUBSTRING) {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("SUBSTRING(" + boolval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return false;
+ }
+
+ boolean boolval2 = Boolean.valueOf(((String) value2).trim()).booleanValue();
+ switch (operation) {
+ case EQUAL : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("EQUAL(" + boolval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return boolval == boolval2;
+ }
+ case APPROX : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("APPROX(" + boolval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return boolval == boolval2;
+ }
+ case GREATER : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("GREATER(" + boolval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return boolval == boolval2;
+ }
+ case LESS : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("LESS(" + boolval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return boolval == boolval2;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean compare_Float(int operation, float floatval, Object value2) {
+ if (operation == SUBSTRING) {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("SUBSTRING(" + floatval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return false;
+ }
+
+ float floatval2;
+ try {
+ floatval2 = Float.parseFloat(((String) value2).trim());
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ switch (operation) {
+ case EQUAL : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("EQUAL(" + floatval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return Float.compare(floatval, floatval2) == 0;
+ }
+ case APPROX : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("APPROX(" + floatval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return Float.compare(floatval, floatval2) == 0;
+ }
+ case GREATER : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("GREATER(" + floatval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return Float.compare(floatval, floatval2) >= 0;
+ }
+ case LESS : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("LESS(" + floatval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return Float.compare(floatval, floatval2) <= 0;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean compare_Double(int operation, double doubleval, Object value2) {
+ if (operation == SUBSTRING) {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("SUBSTRING(" + doubleval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return false;
+ }
+
+ double doubleval2;
+ try {
+ doubleval2 = Double.parseDouble(((String) value2).trim());
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ switch (operation) {
+ case EQUAL : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("EQUAL(" + doubleval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return Double.compare(doubleval, doubleval2) == 0;
+ }
+ case APPROX : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("APPROX(" + doubleval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return Double.compare(doubleval, doubleval2) == 0;
+ }
+ case GREATER : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("GREATER(" + doubleval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return Double.compare(doubleval, doubleval2) >= 0;
+ }
+ case LESS : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("LESS(" + doubleval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return Double.compare(doubleval, doubleval2) <= 0;
+ }
+ }
+
+ return false;
+ }
+
+ private static Object valueOf(Class<?> target, String value2) {
+ do {
+ Method method;
+ try {
+ method = target.getMethod("valueOf", String.class); //$NON-NLS-1$
+ } catch (NoSuchMethodException e) {
+ break;
+ }
+ if (Modifier.isStatic(method.getModifiers()) && target.isAssignableFrom(method.getReturnType())) {
+ setAccessible(method);
+ try {
+ return method.invoke(null, value2.trim());
+ } catch (IllegalAccessException e) {
+ return null;
+ } catch (InvocationTargetException e) {
+ return null;
+ }
+ }
+ } while (false);
+
+ do {
+ Constructor<?> constructor;
+ try {
+ constructor = target.getConstructor(String.class);
+ } catch (NoSuchMethodException e) {
+ break;
+ }
+ setAccessible(constructor);
+ try {
+ return constructor.newInstance(value2.trim());
+ } catch (IllegalAccessException e) {
+ return null;
+ } catch (InvocationTargetException e) {
+ return null;
+ } catch (InstantiationException e) {
+ return null;
+ }
+ } while (false);
+
+ return null;
+ }
+
+ private static void setAccessible(AccessibleObject accessible) {
+ if (!accessible.isAccessible()) {
+ AccessController.doPrivileged(new SetAccessibleAction(accessible));
+ }
+ }
+
+ private boolean compare_Comparable(int operation, Comparable<Object> value1, Object value2) {
+ if (operation == SUBSTRING) {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("SUBSTRING(" + value1 + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return false;
+ }
+ value2 = valueOf(value1.getClass(), (String) value2);
+ if (value2 == null) {
+ return false;
+ }
+
+ try {
+ switch (operation) {
+ case EQUAL : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("EQUAL(" + value1 + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return value1.compareTo(value2) == 0;
+ }
+ case APPROX : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("APPROX(" + value1 + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return value1.compareTo(value2) == 0;
+ }
+ case GREATER : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("GREATER(" + value1 + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return value1.compareTo(value2) >= 0;
+ }
+ case LESS : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("LESS(" + value1 + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return value1.compareTo(value2) <= 0;
+ }
+ }
+ } catch (Exception e) {
+ // if the compareTo method throws an exception; return false
+ return false;
+ }
+ return false;
+ }
+
+ private boolean compare_Unknown(int operation, Object value1, Object value2) { //RFC 59
+ if (operation == SUBSTRING) {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("SUBSTRING(" + value1 + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return false;
+ }
+ value2 = valueOf(value1.getClass(), (String) value2);
+ if (value2 == null) {
+ return false;
+ }
+
+ try {
+ switch (operation) {
+ case EQUAL : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("EQUAL(" + value1 + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return value1.equals(value2);
+ }
+ case APPROX : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("APPROX(" + value1 + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return value1.equals(value2);
+ }
+ case GREATER : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("GREATER(" + value1 + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return value1.equals(value2);
+ }
+ case LESS : {
+ if (Debug.DEBUG_FILTER) {
+ Debug.println("LESS(" + value1 + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return value1.equals(value2);
+ }
+ }
+ } catch (Exception e) {
+ // if the equals method throws an exception; return false
+ return false;
+ }
+
+ return false;
+ }
+
+ /**
+ * Map a string for an APPROX (~=) comparison.
+ *
+ * This implementation removes white spaces.
+ * This is the minimum implementation allowed by
+ * the OSGi spec.
+ *
+ * @param input Input string.
+ * @return String ready for APPROX comparison.
+ */
+ private static String approxString(String input) {
+ boolean changed = false;
+ char[] output = input.toCharArray();
+ int cursor = 0;
+ for (char c : output) {
+ if (Character.isWhitespace(c)) {
+ changed = true;
+ continue;
+ }
+
+ output[cursor] = c;
+ cursor++;
+ }
+
+ return changed ? new String(output, 0, cursor) : input;
+ }
+
+ /**
+ * Returns the leftmost required objectClass value for the filter to evaluate to true.
+ *
+ * @return The leftmost required objectClass value or null if none could be determined.
+ */
+ public String getRequiredObjectClass() {
+ return getPrimaryKeyValue(Constants.OBJECTCLASS);
+ }
+
+ /**
+ * Returns the leftmost required primary key value for the filter to evaluate to true.
+ * This is useful for indexing candidates to match against this filter.
+ * @param primaryKey the primary key
+ * @return The leftmost required primary key value or null if none could be determined.
+ */
+ public String getPrimaryKeyValue(String primaryKey) {
+ // just checking for simple filters here where primaryKey is the only attr or it is one attr of a base '&' clause
+ // (primaryKey=org.acme.BrickService) OK
+ // (&(primaryKey=org.acme.BrickService)(|(vendor=IBM)(vendor=SUN))) OK
+ // (primaryKey=org.acme.*) NOT OK
+ // (|(primaryKey=org.acme.BrickService)(primaryKey=org.acme.CementService)) NOT OK
+ // (&(primaryKey=org.acme.BrickService)(primaryKey=org.acme.CementService)) OK but only the first objectClass is returned
+ switch (op) {
+ case EQUAL :
+ if (attr.equalsIgnoreCase(primaryKey) && (value instanceof String))
+ return (String) value;
+ break;
+ case AND :
+ FilterImpl[] clauses = (FilterImpl[]) value;
+ for (FilterImpl clause : clauses)
+ if (clause.op == EQUAL) {
+ String result = clause.getPrimaryKeyValue(primaryKey);
+ if (result != null)
+ return result;
+ }
+ break;
+ }
+ return null;
+ }
+
+ /**
+ * Returns all the attributes contained within this filter
+ * @return all the attributes contained within this filter
+ */
+ public String[] getAttributes() {
+ List<String> results = new ArrayList<String>();
+ getAttributesInternal(results);
+ return results.toArray(new String[results.size()]);
+ }
+
+ private void getAttributesInternal(List<String> results) {
+ if (value instanceof FilterImpl[]) {
+ FilterImpl[] children = (FilterImpl[]) value;
+ for (FilterImpl child : children)
+ child.getAttributesInternal(results);
+ return;
+ } else if (value instanceof FilterImpl) {
+ // The NOT operation only has one child filter (bug 188075)
+ FilterImpl child = ((FilterImpl) value);
+ child.getAttributesInternal(results);
+ return;
+ }
+ if (attr != null)
+ results.add(attr);
+ }
+
+ /**
+ * Parser class for OSGi filter strings. This class parses
+ * the complete filter string and builds a tree of Filter
+ * objects rooted at the parent.
+ */
+ private static class Parser {
+ private final String filterstring;
+ private final char[] filterChars;
+ private int pos;
+
+ Parser(String filterstring) {
+ this.filterstring = filterstring;
+ filterChars = filterstring.toCharArray();
+ pos = 0;
+ }
+
+ FilterImpl parse() throws InvalidSyntaxException {
+ FilterImpl filter;
+ try {
+ filter = parse_filter();
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw new InvalidSyntaxException(Msg.FILTER_TERMINATED_ABRUBTLY, filterstring);
+ }
+
+ if (pos != filterChars.length) {
+ throw new InvalidSyntaxException(NLS.bind(Msg.FILTER_TRAILING_CHARACTERS, filterstring.substring(pos)), filterstring);
+ }
+ return filter;
+ }
+
+ private FilterImpl parse_filter() throws InvalidSyntaxException {
+ FilterImpl filter;
+ skipWhiteSpace();
+
+ if (filterChars[pos] != '(') {
+ throw new InvalidSyntaxException(NLS.bind(Msg.FILTER_MISSING_LEFTPAREN, filterstring.substring(pos)), filterstring);
+ }
+
+ pos++;
+
+ filter = parse_filtercomp();
+
+ skipWhiteSpace();
+
+ if (filterChars[pos] != ')') {
+ throw new InvalidSyntaxException(NLS.bind(Msg.FILTER_MISSING_RIGHTPAREN, filterstring.substring(pos)), filterstring);
+ }
+
+ pos++;
+
+ skipWhiteSpace();
+
+ return filter;
+ }
+
+ private FilterImpl parse_filtercomp() throws InvalidSyntaxException {
+ skipWhiteSpace();
+
+ char c = filterChars[pos];
+
+ switch (c) {
+ case '&' : {
+ pos++;
+ return parse_and();
+ }
+ case '|' : {
+ pos++;
+ return parse_or();
+ }
+ case '!' : {
+ pos++;
+ return parse_not();
+ }
+ }
+ return parse_item();
+ }
+
+ private FilterImpl parse_and() throws InvalidSyntaxException {
+ int lookahead = pos;
+ skipWhiteSpace();
+
+ if (filterChars[pos] != '(') {
+ pos = lookahead - 1;
+ return parse_item();
+ }
+
+ List<FilterImpl> operands = new ArrayList<FilterImpl>(10);
+
+ while (filterChars[pos] == '(') {
+ FilterImpl child = parse_filter();
+ operands.add(child);
+ }
+
+ return new FilterImpl(FilterImpl.AND, null, operands.toArray(new FilterImpl[operands.size()]));
+ }
+
+ private FilterImpl parse_or() throws InvalidSyntaxException {
+ int lookahead = pos;
+ skipWhiteSpace();
+
+ if (filterChars[pos] != '(') {
+ pos = lookahead - 1;
+ return parse_item();
+ }
+
+ List<FilterImpl> operands = new ArrayList<FilterImpl>(10);
+
+ while (filterChars[pos] == '(') {
+ FilterImpl child = parse_filter();
+ operands.add(child);
+ }
+
+ return new FilterImpl(FilterImpl.OR, null, operands.toArray(new FilterImpl[operands.size()]));
+ }
+
+ private FilterImpl parse_not() throws InvalidSyntaxException {
+ int lookahead = pos;
+ skipWhiteSpace();
+
+ if (filterChars[pos] != '(') {
+ pos = lookahead - 1;
+ return parse_item();
+ }
+
+ FilterImpl child = parse_filter();
+
+ return new FilterImpl(FilterImpl.NOT, null, child);
+ }
+
+ private FilterImpl parse_item() throws InvalidSyntaxException {
+ String attr = parse_attr();
+
+ skipWhiteSpace();
+
+ switch (filterChars[pos]) {
+ case '~' : {
+ if (filterChars[pos + 1] == '=') {
+ pos += 2;
+ return new FilterImpl(FilterImpl.APPROX, attr, parse_value());
+ }
+ break;
+ }
+ case '>' : {
+ if (filterChars[pos + 1] == '=') {
+ pos += 2;
+ return new FilterImpl(FilterImpl.GREATER, attr, parse_value());
+ }
+ break;
+ }
+ case '<' : {
+ if (filterChars[pos + 1] == '=') {
+ pos += 2;
+ return new FilterImpl(FilterImpl.LESS, attr, parse_value());
+ }
+ break;
+ }
+ case '=' : {
+ if (filterChars[pos + 1] == '*') {
+ int oldpos = pos;
+ pos += 2;
+ skipWhiteSpace();
+ if (filterChars[pos] == ')') {
+ return new FilterImpl(FilterImpl.PRESENT, attr, null);
+ }
+ pos = oldpos;
+ }
+
+ pos++;
+ Object string = parse_substring();
+
+ if (string instanceof String) {
+ return new FilterImpl(FilterImpl.EQUAL, attr, string);
+ }
+ return new FilterImpl(FilterImpl.SUBSTRING, attr, string);
+ }
+ }
+
+ throw new InvalidSyntaxException(NLS.bind(Msg.FILTER_INVALID_OPERATOR, filterstring.substring(pos)), filterstring);
+ }
+
+ private String parse_attr() throws InvalidSyntaxException {
+ skipWhiteSpace();
+
+ int begin = pos;
+ int end = pos;
+
+ char c = filterChars[pos];
+
+ while (c != '~' && c != '<' && c != '>' && c != '=' && c != '(' && c != ')') {
+ pos++;
+
+ if (!Character.isWhitespace(c)) {
+ end = pos;
+ }
+
+ c = filterChars[pos];
+ }
+
+ int length = end - begin;
+
+ if (length == 0) {
+ throw new InvalidSyntaxException(NLS.bind(Msg.FILTER_MISSING_ATTR, filterstring.substring(pos)), filterstring);
+ }
+
+ return new String(filterChars, begin, length);
+ }
+
+ private String parse_value() throws InvalidSyntaxException {
+ StringBuffer sb = new StringBuffer(filterChars.length - pos);
+
+ parseloop: while (true) {
+ char c = filterChars[pos];
+
+ switch (c) {
+ case ')' : {
+ break parseloop;
+ }
+
+ case '(' : {
+ throw new InvalidSyntaxException(NLS.bind(Msg.FILTER_INVALID_VALUE, filterstring.substring(pos)), filterstring);
+ }
+
+ case '\\' : {
+ pos++;
+ c = filterChars[pos];
+ /* fall through into default */
+ }
+
+ default : {
+ sb.append(c);
+ pos++;
+ break;
+ }
+ }
+ }
+
+ if (sb.length() == 0) {
+ throw new InvalidSyntaxException(NLS.bind(Msg.FILTER_MISSING_VALUE, filterstring.substring(pos)), filterstring);
+ }
+
+ return sb.toString();
+ }
+
+ private Object parse_substring() throws InvalidSyntaxException {
+ StringBuffer sb = new StringBuffer(filterChars.length - pos);
+
+ List<String> operands = new ArrayList<String>(10);
+
+ parseloop: while (true) {
+ char c = filterChars[pos];
+
+ switch (c) {
+ case ')' : {
+ if (sb.length() > 0) {
+ operands.add(sb.toString());
+ }
+
+ break parseloop;
+ }
+
+ case '(' : {
+ throw new InvalidSyntaxException(NLS.bind(Msg.FILTER_INVALID_VALUE, filterstring.substring(pos)), filterstring);
+ }
+
+ case '*' : {
+ if (sb.length() > 0) {
+ operands.add(sb.toString());
+ }
+
+ sb.setLength(0);
+
+ operands.add(null);
+ pos++;
+
+ break;
+ }
+
+ case '\\' : {
+ pos++;
+ c = filterChars[pos];
+ /* fall through into default */
+ }
+
+ default : {
+ sb.append(c);
+ pos++;
+ break;
+ }
+ }
+ }
+
+ int size = operands.size();
+
+ if (size == 0) {
+ return ""; //$NON-NLS-1$
+ }
+
+ if (size == 1) {
+ Object single = operands.get(0);
+
+ if (single != null) {
+ return single;
+ }
+ }
+
+ return operands.toArray(new String[size]);
+ }
+
+ private void skipWhiteSpace() {
+ for (int length = filterChars.length; (pos < length) && Character.isWhitespace(filterChars[pos]);) {
+ pos++;
+ }
+ }
+ }
+
+ /**
+ * This Dictionary is used for key lookup from a ServiceReference during
+ * filter evaluation. This Dictionary implementation only supports the get
+ * operation using a String key as no other operations are used by the
+ * Filter implementation.
+ *
+ */
+ private static class ServiceReferenceDictionary extends Dictionary<String, Object> {
+ private final ServiceReference<?> reference;
+
+ ServiceReferenceDictionary(ServiceReference<?> reference) {
+ this.reference = reference;
+ }
+
+ public Object get(Object key) {
+ if (reference == null) {
+ return null;
+ }
+ return reference.getProperty((String) key);
+ }
+
+ public boolean isEmpty() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Enumeration<String> keys() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Enumeration<Object> elements() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object put(String key, Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object remove(Object key) {
+ throw new UnsupportedOperationException();
+ }
+
+ public int size() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ private static class SetAccessibleAction implements PrivilegedAction<Object> {
+ private final AccessibleObject accessible;
+
+ SetAccessibleAction(AccessibleObject accessible) {
+ this.accessible = accessible;
+ }
+
+ public Object run() {
+ accessible.setAccessible(true);
+ return null;
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/Framework.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/Framework.java
new file mode 100644
index 000000000..d3aeaf645
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/Framework.java
@@ -0,0 +1,2007 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.framework.internal.core;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.net.*;
+import java.security.*;
+import java.util.*;
+import org.eclipse.core.runtime.internal.adaptor.ContextFinder;
+import org.eclipse.osgi.baseadaptor.BaseAdaptor;
+import org.eclipse.osgi.framework.adaptor.*;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.framework.eventmgr.*;
+import org.eclipse.osgi.framework.internal.protocol.ContentHandlerFactory;
+import org.eclipse.osgi.framework.internal.protocol.StreamHandlerFactory;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+import org.eclipse.osgi.framework.util.SecureAction;
+import org.eclipse.osgi.internal.loader.*;
+import org.eclipse.osgi.internal.permadmin.EquinoxSecurityManager;
+import org.eclipse.osgi.internal.permadmin.SecurityAdmin;
+import org.eclipse.osgi.internal.profile.Profile;
+import org.eclipse.osgi.internal.serviceregistry.*;
+import org.eclipse.osgi.signedcontent.SignedContentFactory;
+import org.eclipse.osgi.util.ManifestElement;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.*;
+import org.osgi.framework.hooks.bundle.*;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * Core OSGi Framework class.
+ */
+public class Framework implements EventPublisher, Runnable {
+ // System property used to set the context classloader parent classloader type (ccl is the default)
+ private static final String PROP_CONTEXTCLASSLOADER_PARENT = "osgi.contextClassLoaderParent"; //$NON-NLS-1$
+ private static final String CONTEXTCLASSLOADER_PARENT_APP = "app"; //$NON-NLS-1$
+ private static final String CONTEXTCLASSLOADER_PARENT_EXT = "ext"; //$NON-NLS-1$
+ private static final String CONTEXTCLASSLOADER_PARENT_BOOT = "boot"; //$NON-NLS-1$
+ private static final String CONTEXTCLASSLOADER_PARENT_FWK = "fwk"; //$NON-NLS-1$
+
+ public static final String PROP_FRAMEWORK_THREAD = "osgi.framework.activeThreadType"; //$NON-NLS-1$
+ public static final String THREAD_NORMAL = "normal"; //$NON-NLS-1$
+ public static final String PROP_EQUINOX_SECURITY = "eclipse.security"; //$NON-NLS-1$
+ public static final String SECURITY_OSGI = "osgi"; //$NON-NLS-1$
+
+ private static String J2SE = "J2SE-"; //$NON-NLS-1$
+ private static String JAVASE = "JavaSE-"; //$NON-NLS-1$
+ private static String PROFILE_EXT = ".profile"; //$NON-NLS-1$
+ /** FrameworkAdaptor specific functions. */
+ protected FrameworkAdaptor adaptor;
+ /** Framework properties object. A reference to the
+ * System.getProperies() object. The properties from
+ * the adaptor will be merged into these properties.
+ */
+ protected Properties properties;
+ /** Has the framework been started */
+ protected boolean active;
+ /** Event indicating the reason for shutdown*/
+ private FrameworkEvent[] shutdownEvent;
+ /** The bundles installed in the framework */
+ protected BundleRepository bundles;
+ /** Package Admin object. This object manages the exported packages. */
+ protected PackageAdminImpl packageAdmin;
+ /** PermissionAdmin and ConditionalPermissionAdmin impl. This object manages the bundle permissions. */
+ protected SecurityAdmin securityAdmin;
+ /** Startlevel object. This object manages the framework and bundle startlevels */
+ protected StartLevelManager startLevelManager;
+ /** The ServiceRegistry */
+ private ServiceRegistry serviceRegistry;
+ private final int BSN_VERSION;
+ private static final int BSN_VERSION_SINGLE = 1;
+ private static final int BSN_VERSION_MULTIPLE = 2;
+ private static final int BSN_VERSION_MANAGED = 3;
+
+ /*
+ * The following maps objects keep track of event listeners
+ * by BundleContext. Each element is a Map that is the set
+ * of event listeners for a particular BundleContext. The max number of
+ * elements each of the following maps will have is the number of bundles
+ * installed in the Framework.
+ */
+ // Map of BundleContexts for bundle's BundleListeners.
+ private final Map<BundleContextImpl, CopyOnWriteIdentityMap<BundleListener, BundleListener>> allBundleListeners = new HashMap<BundleContextImpl, CopyOnWriteIdentityMap<BundleListener, BundleListener>>();
+ protected static final int BUNDLEEVENT = 1;
+ // Map of BundleContexts for bundle's SynchronousBundleListeners.
+ private final Map<BundleContextImpl, CopyOnWriteIdentityMap<SynchronousBundleListener, SynchronousBundleListener>> allSyncBundleListeners = new HashMap<BundleContextImpl, CopyOnWriteIdentityMap<SynchronousBundleListener, SynchronousBundleListener>>();
+ protected static final int BUNDLEEVENTSYNC = 2;
+ /* SERVICEEVENT(3) is now handled by ServiceRegistry */
+ // Map of BundleContexts for bundle's FrameworkListeners.
+ private final Map<BundleContextImpl, CopyOnWriteIdentityMap<FrameworkListener, FrameworkListener>> allFrameworkListeners = new HashMap<BundleContextImpl, CopyOnWriteIdentityMap<FrameworkListener, FrameworkListener>>();
+ protected static final int FRAMEWORKEVENT = 4;
+ protected static final int BATCHEVENT_BEGIN = Integer.MIN_VALUE + 1;
+ protected static final int BATCHEVENT_END = Integer.MIN_VALUE;
+ static final String eventHookName = EventHook.class.getName();
+ static final String findHookName = FindHook.class.getName();
+ static final String collisionHookName = CollisionHook.class.getName();
+ /** EventManager for event delivery. */
+ protected EventManager eventManager;
+ /* Reservation object for install synchronization */
+ private Map<String, Thread> installLock;
+ /** System Bundle object */
+ protected InternalSystemBundle systemBundle;
+ private String[] bootDelegation;
+ private String[] bootDelegationStems;
+ private boolean bootDelegateAll = false;
+ public final boolean contextBootDelegation = "true".equals(FrameworkProperties.getProperty("osgi.context.bootdelegation", "true")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ public final boolean compatibiltyBootDelegation = "true".equals(FrameworkProperties.getProperty(Constants.OSGI_COMPATIBILITY_BOOTDELEGATION, "true")); //$NON-NLS-1$ //$NON-NLS-2$
+ private final boolean allowRefreshDuplicateBSN = Boolean.TRUE.toString().equals(FrameworkProperties.getProperty(Constants.REFRESH_DUPLICATE_BSN, "true")); //$NON-NLS-1$
+ ClassLoaderDelegateHook[] delegateHooks;
+ private volatile boolean forcedRestart = false;
+ /**
+ * The AliasMapper used to alias OS Names.
+ */
+ protected static AliasMapper aliasMapper = new AliasMapper();
+ SecureAction secureAction = AccessController.doPrivileged(SecureAction.createSecureAction());
+ // cache of AdminPermissions keyed by Bundle ID
+ private final Map<Long, Map<String, AdminPermission>> adminPermissions = new HashMap<Long, Map<String, AdminPermission>>();
+
+ // we need to hold these so that we can unregister them at shutdown
+ private StreamHandlerFactory streamHandlerFactory;
+ private ContentHandlerFactory contentHandlerFactory;
+
+ private volatile ServiceTracker<SignedContentFactory, SignedContentFactory> signedContentFactory;
+ private volatile ContextFinder contextFinder;
+
+ /*
+ * We need to make sure that the GetDataFileAction class loads early to prevent a ClassCircularityError when checking permissions.
+ * see bug 161561
+ */
+ static {
+ Class<?> c;
+ c = GetDataFileAction.class;
+ c.getName(); // to prevent compiler warnings
+ }
+
+ static class GetDataFileAction implements PrivilegedAction<File> {
+ private AbstractBundle bundle;
+ private String filename;
+
+ public GetDataFileAction(AbstractBundle bundle, String filename) {
+ this.bundle = bundle;
+ this.filename = filename;
+ }
+
+ public File run() {
+ return bundle.getBundleData().getDataFile(filename);
+ }
+ }
+
+ /**
+ * Constructor for the Framework instance. This method initializes the
+ * framework to an unlaunched state.
+ *
+ */
+ public Framework(FrameworkAdaptor adaptor) {
+ if (Profile.PROFILE && Profile.STARTUP)
+ Profile.logEnter("Framework.initialze()", null); //$NON-NLS-1$
+ String bsnVersion = FrameworkProperties.getProperty(Constants.FRAMEWORK_BSNVERSION);
+ if (Constants.FRAMEWORK_BSNVERSION_SINGLE.equals(bsnVersion)) {
+ BSN_VERSION = BSN_VERSION_SINGLE;
+ } else if (Constants.FRAMEWORK_BSNVERSION_MULTIPLE.equals(bsnVersion)) {
+ BSN_VERSION = BSN_VERSION_MULTIPLE;
+ } else {
+ BSN_VERSION = BSN_VERSION_MANAGED;
+ }
+ long start = System.currentTimeMillis();
+ this.adaptor = adaptor;
+ delegateHooks = adaptor instanceof BaseAdaptor ? ((BaseAdaptor) adaptor).getHookRegistry().getClassLoaderDelegateHooks() : null;
+ active = false;
+ installSecurityManager();
+ if (Debug.DEBUG_SECURITY) {
+ Debug.println("SecurityManager: " + System.getSecurityManager()); //$NON-NLS-1$
+ Debug.println("ProtectionDomain of Framework.class: \n" + this.getClass().getProtectionDomain()); //$NON-NLS-1$
+ }
+ setNLSFrameworkLog();
+ // initialize ContextFinder
+ initializeContextFinder();
+ /* initialize the adaptor */
+ adaptor.initialize(this);
+ if (Profile.PROFILE && Profile.STARTUP)
+ Profile.logTime("Framework.initialze()", "adapter initialized"); //$NON-NLS-1$//$NON-NLS-2$
+ try {
+ adaptor.initializeStorage();
+ } catch (IOException e) /* fatal error */{
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ if (Profile.PROFILE && Profile.STARTUP)
+ Profile.logTime("Framework.initialze()", "adapter storage initialized"); //$NON-NLS-1$//$NON-NLS-2$
+ /*
+ * This must be done before calling any of the framework getProperty
+ * methods.
+ */
+ initializeProperties(adaptor.getProperties());
+ /* initialize admin objects */
+ packageAdmin = new PackageAdminImpl(this);
+ try {
+ // always create security admin even with security off
+ securityAdmin = new SecurityAdmin(null, this, adaptor.getPermissionStorage());
+ } catch (IOException e) /* fatal error */{
+ e.printStackTrace();
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ if (Profile.PROFILE && Profile.STARTUP)
+ Profile.logTime("Framework.initialze()", "done init props & new PermissionAdminImpl"); //$NON-NLS-1$//$NON-NLS-2$
+ startLevelManager = new StartLevelManager(this);
+ /* create the event manager and top level event dispatchers */
+ eventManager = new EventManager("Framework Event Dispatcher"); //$NON-NLS-1$
+ if (Profile.PROFILE && Profile.STARTUP)
+ Profile.logTime("Framework.initialze()", "done new EventManager"); //$NON-NLS-1$ //$NON-NLS-2$
+ /* create the service registry */
+ serviceRegistry = new ServiceRegistry(this);
+ // Initialize the installLock; there is no way of knowing
+ // what the initial size should be, at most it will be the number
+ // of threads trying to install a bundle (probably a very low number).
+ installLock = new HashMap<String, Thread>(10);
+ /* create the system bundle */
+ createSystemBundle();
+ loadVMProfile(); // load VM profile after the system bundle has been created
+ setBootDelegation(); //set boot delegation property after system exports have been set
+ if (Profile.PROFILE && Profile.STARTUP)
+ Profile.logTime("Framework.initialze()", "done createSystemBundle"); //$NON-NLS-1$ //$NON-NLS-2$
+ /* install URLStreamHandlerFactory */
+ installURLStreamHandlerFactory(systemBundle.context, adaptor);
+ /* install ContentHandlerFactory for OSGi URLStreamHandler support */
+ installContentHandlerFactory(systemBundle.context, adaptor);
+ if (Profile.PROFILE && Profile.STARTUP)
+ Profile.logTime("Framework.initialze()", "done new URLStream/Content HandlerFactory"); //$NON-NLS-1$//$NON-NLS-2$
+ /* create bundle objects for all installed bundles. */
+ BundleData[] bundleDatas = adaptor.getInstalledBundles();
+ bundles = new BundleRepository(bundleDatas == null ? 10 : bundleDatas.length + 1);
+ /* add the system bundle to the Bundle Repository */
+ bundles.add(systemBundle);
+ if (bundleDatas != null) {
+ for (int i = 0; i < bundleDatas.length; i++) {
+ try {
+ AbstractBundle bundle = AbstractBundle.createBundle(bundleDatas[i], this, true);
+ bundles.add(bundle);
+ } catch (BundleException be) {
+ // This is not a fatal error. Publish the framework event.
+ publishFrameworkEvent(FrameworkEvent.ERROR, systemBundle, be);
+ }
+ }
+ }
+ if (Debug.DEBUG_GENERAL)
+ System.out.println("Initialize the framework: " + (System.currentTimeMillis() - start)); //$NON-NLS-1$
+ if (Profile.PROFILE && Profile.STARTUP)
+ Profile.logExit("Framework.initialize()"); //$NON-NLS-1$
+ }
+
+ public FrameworkAdaptor getAdaptor() {
+ return adaptor;
+ }
+
+ public ClassLoaderDelegateHook[] getDelegateHooks() {
+ return delegateHooks;
+ }
+
+ public ServiceRegistry getServiceRegistry() {
+ return serviceRegistry;
+ }
+
+ private void setNLSFrameworkLog() {
+ try {
+ Field frameworkLogField = NLS.class.getDeclaredField("frameworkLog"); //$NON-NLS-1$
+ frameworkLogField.setAccessible(true);
+ frameworkLogField.set(null, adaptor.getFrameworkLog());
+ } catch (Exception e) {
+ adaptor.getFrameworkLog().log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, e.getMessage(), 0, e, null));
+ }
+ }
+
+ private void createSystemBundle() {
+ try {
+ systemBundle = new InternalSystemBundle(this);
+ systemBundle.getBundleData().setBundle(systemBundle);
+ } catch (BundleException e) { // fatal error
+ e.printStackTrace();
+ throw new RuntimeException(NLS.bind(Msg.OSGI_SYSTEMBUNDLE_CREATE_EXCEPTION, e.getMessage()), e);
+ }
+ }
+
+ /**
+ * Initialize the System properties by copying properties from the adaptor
+ * properties object. This method is called by the initialize method.
+ *
+ */
+ protected void initializeProperties(Properties adaptorProperties) {
+ properties = FrameworkProperties.getProperties();
+ Enumeration<?> enumKeys = adaptorProperties.propertyNames();
+ while (enumKeys.hasMoreElements()) {
+ String key = (String) enumKeys.nextElement();
+ if (properties.getProperty(key) == null) {
+ properties.put(key, adaptorProperties.getProperty(key));
+ }
+ }
+ properties.put(Constants.FRAMEWORK_VENDOR, Constants.OSGI_FRAMEWORK_VENDOR);
+ properties.put(Constants.FRAMEWORK_VERSION, Constants.OSGI_FRAMEWORK_VERSION);
+ String value = properties.getProperty(Constants.FRAMEWORK_PROCESSOR);
+ if (value == null) {
+ value = properties.getProperty(Constants.JVM_OS_ARCH);
+ if (value != null) {
+ properties.put(Constants.FRAMEWORK_PROCESSOR, aliasMapper.aliasProcessor(value));
+ }
+ }
+ value = properties.getProperty(Constants.FRAMEWORK_OS_NAME);
+ if (value == null) {
+ value = properties.getProperty(Constants.JVM_OS_NAME);
+ try {
+ String canonicalValue = (String) aliasMapper.aliasOSName(value);
+ if (canonicalValue != null) {
+ value = canonicalValue;
+ }
+ } catch (ClassCastException ex) {
+ //A vector was returned from the alias mapper.
+ //The alias mapped to more than one canonical value
+ //such as "win32" for example
+ }
+ if (value != null) {
+ properties.put(Constants.FRAMEWORK_OS_NAME, value);
+ }
+ }
+ value = properties.getProperty(Constants.FRAMEWORK_OS_VERSION);
+ if (value == null) {
+ value = properties.getProperty(Constants.JVM_OS_VERSION);
+ if (value != null) {
+ // only use the value upto the first space
+ int space = value.indexOf(' ');
+ if (space > 0) {
+ value = value.substring(0, space);
+ }
+ // fix up cases where the os version does not make a valid Version string.
+ int major = 0, minor = 0, micro = 0;
+ String qualifier = ""; //$NON-NLS-1$
+ try {
+ StringTokenizer st = new StringTokenizer(value, ".", true); //$NON-NLS-1$
+ major = parseVersionInt(st.nextToken());
+
+ if (st.hasMoreTokens()) {
+ st.nextToken(); // consume delimiter
+ minor = parseVersionInt(st.nextToken());
+
+ if (st.hasMoreTokens()) {
+ st.nextToken(); // consume delimiter
+ micro = parseVersionInt(st.nextToken());
+
+ if (st.hasMoreTokens()) {
+ st.nextToken(); // consume delimiter
+ qualifier = st.nextToken();
+ }
+ }
+ }
+ } catch (NoSuchElementException e) {
+ // ignore, use the values parsed so far
+ }
+ try {
+ value = new Version(major, minor, micro, qualifier).toString();
+ } catch (IllegalArgumentException e) {
+ // must be an invalid qualifier; just ignore it
+ value = new Version(major, minor, micro).toString();
+ }
+ properties.put(Constants.FRAMEWORK_OS_VERSION, value);
+ }
+ }
+ value = properties.getProperty(Constants.FRAMEWORK_LANGUAGE);
+ if (value == null)
+ // set the value of the framework language property
+ properties.put(Constants.FRAMEWORK_LANGUAGE, Locale.getDefault().getLanguage());
+ // set the support properties for fragments and require-bundle (bug 173090)
+ properties.put(Constants.SUPPORTS_FRAMEWORK_FRAGMENT, "true"); //$NON-NLS-1$
+ properties.put(Constants.SUPPORTS_FRAMEWORK_REQUIREBUNDLE, "true"); //$NON-NLS-1$
+ properties.put(Constants.FRAMEWORK_UUID, new UniversalUniqueIdentifier().toString());
+ }
+
+ private int parseVersionInt(String value) {
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ // try up to the first non-number char
+ StringBuffer sb = new StringBuffer(value.length());
+ char[] chars = value.toCharArray();
+ for (int i = 0; i < chars.length; i++) {
+ if (!Character.isDigit(chars[i]))
+ break;
+ sb.append(chars[i]);
+ }
+ if (sb.length() > 0)
+ return Integer.parseInt(sb.toString());
+ return 0;
+ }
+ }
+
+ private void setBootDelegation() {
+ // set the boot delegation according to the osgi boot delegation property
+ String bootDelegationProp = properties.getProperty(Constants.FRAMEWORK_BOOTDELEGATION);
+ if (bootDelegationProp == null)
+ return;
+ if (bootDelegationProp.trim().length() == 0)
+ return;
+ String[] bootPackages = ManifestElement.getArrayFromList(bootDelegationProp);
+ List<String> exactMatch = new ArrayList<String>(bootPackages.length);
+ List<String> stemMatch = new ArrayList<String>(bootPackages.length);
+ for (int i = 0; i < bootPackages.length; i++) {
+ if (bootPackages[i].equals("*")) { //$NON-NLS-1$
+ bootDelegateAll = true;
+ return;
+ } else if (bootPackages[i].endsWith("*")) { //$NON-NLS-1$
+ if (bootPackages[i].length() > 2 && bootPackages[i].endsWith(".*")) //$NON-NLS-1$
+ stemMatch.add(bootPackages[i].substring(0, bootPackages[i].length() - 1));
+ } else {
+ exactMatch.add(bootPackages[i]);
+ }
+ }
+ if (!exactMatch.isEmpty())
+ bootDelegation = exactMatch.toArray(new String[exactMatch.size()]);
+ if (!stemMatch.isEmpty())
+ bootDelegationStems = stemMatch.toArray(new String[stemMatch.size()]);
+ }
+
+ @SuppressWarnings("deprecation")
+ private void loadVMProfile() {
+ Properties profileProps = findVMProfile();
+ String systemExports = properties.getProperty(Constants.FRAMEWORK_SYSTEMPACKAGES);
+ // set the system exports property using the vm profile; only if the property is not already set
+ if (systemExports == null) {
+ systemExports = profileProps.getProperty(Constants.FRAMEWORK_SYSTEMPACKAGES);
+ if (systemExports != null)
+ properties.put(Constants.FRAMEWORK_SYSTEMPACKAGES, systemExports);
+ }
+ // set the org.osgi.framework.bootdelegation property according to the java profile
+ String type = properties.getProperty(Constants.OSGI_JAVA_PROFILE_BOOTDELEGATION); // a null value means ignore
+ String profileBootDelegation = profileProps.getProperty(Constants.FRAMEWORK_BOOTDELEGATION);
+ if (Constants.OSGI_BOOTDELEGATION_OVERRIDE.equals(type)) {
+ if (profileBootDelegation == null)
+ properties.remove(Constants.FRAMEWORK_BOOTDELEGATION); // override with a null value
+ else
+ properties.put(Constants.FRAMEWORK_BOOTDELEGATION, profileBootDelegation); // override with the profile value
+ } else if (Constants.OSGI_BOOTDELEGATION_NONE.equals(type))
+ properties.remove(Constants.FRAMEWORK_BOOTDELEGATION); // remove the bootdelegation property in case it was set
+ // set the org.osgi.framework.executionenvironment property according to the java profile
+ if (properties.getProperty(Constants.FRAMEWORK_EXECUTIONENVIRONMENT) == null) {
+ // get the ee from the java profile; if no ee is defined then try the java profile name
+ String ee = profileProps.getProperty(Constants.FRAMEWORK_EXECUTIONENVIRONMENT, profileProps.getProperty(Constants.OSGI_JAVA_PROFILE_NAME));
+ if (ee != null)
+ properties.put(Constants.FRAMEWORK_EXECUTIONENVIRONMENT, ee);
+ }
+ // set the org.osgi.framework.system.capabilities property according to the java profile
+ if (properties.getProperty(Constants.FRAMEWORK_SYSTEMCAPABILITIES) == null) {
+ String systemCapabilities = profileProps.getProperty(Constants.FRAMEWORK_SYSTEMCAPABILITIES);
+ if (systemCapabilities != null)
+ properties.put(Constants.FRAMEWORK_SYSTEMCAPABILITIES, systemCapabilities);
+ }
+ }
+
+ private Properties findVMProfile() {
+ Properties result = new Properties();
+ // Find the VM profile name using J2ME properties
+ String j2meConfig = properties.getProperty(Constants.J2ME_MICROEDITION_CONFIGURATION);
+ String j2meProfiles = properties.getProperty(Constants.J2ME_MICROEDITION_PROFILES);
+ String vmProfile = null;
+ String javaEdition = null;
+ Version javaVersion = null;
+ if (j2meConfig != null && j2meConfig.length() > 0 && j2meProfiles != null && j2meProfiles.length() > 0) {
+ // save the vmProfile based off of the config and profile
+ // use the last profile; assuming that is the highest one
+ String[] j2meProfileList = ManifestElement.getArrayFromList(j2meProfiles, " "); //$NON-NLS-1$
+ if (j2meProfileList != null && j2meProfileList.length > 0)
+ vmProfile = j2meConfig + '_' + j2meProfileList[j2meProfileList.length - 1];
+ } else {
+ // No J2ME properties; use J2SE properties
+ // Note that the CDC spec appears not to require VM implementations to set the
+ // javax.microedition properties!! So we will try to fall back to the
+ // java.specification.name property, but this is pretty ridiculous!!
+ String javaSpecVersion = properties.getProperty("java.specification.version"); //$NON-NLS-1$
+ // set the profile and EE based off of the java.specification.version
+ // TODO We assume J2ME Foundation and J2SE here. need to support other profiles J2EE ...
+ if (javaSpecVersion != null) {
+ StringTokenizer st = new StringTokenizer(javaSpecVersion, " _-"); //$NON-NLS-1$
+ javaSpecVersion = st.nextToken();
+ String javaSpecName = properties.getProperty("java.specification.name"); //$NON-NLS-1$
+ // See bug 291269 we check for Foundation Specification and Foundation Profile Specification
+ if (javaSpecName != null && (javaSpecName.indexOf("Foundation Specification") >= 0 || javaSpecName.indexOf("Foundation Profile Specification") >= 0)) //$NON-NLS-1$ //$NON-NLS-2$
+ vmProfile = "CDC-" + javaSpecVersion + "_Foundation-" + javaSpecVersion; //$NON-NLS-1$ //$NON-NLS-2$
+ else {
+ // look for JavaSE if 1.6 or greater; otherwise look for J2SE
+ Version v16 = new Version("1.6"); //$NON-NLS-1$
+ javaEdition = J2SE;
+ try {
+ javaVersion = new Version(javaSpecVersion);
+ if (v16.compareTo(javaVersion) <= 0)
+ javaEdition = JAVASE;
+ } catch (IllegalArgumentException e) {
+ // do nothing
+ }
+ vmProfile = javaEdition + javaSpecVersion;
+ }
+ }
+ }
+ URL url = null;
+ // check for the java profile property for a url
+ String propJavaProfile = FrameworkProperties.getProperty(Constants.OSGI_JAVA_PROFILE);
+ if (propJavaProfile != null)
+ try {
+ // we assume a URL
+ url = new URL(propJavaProfile);
+ } catch (MalformedURLException e1) {
+ // try using a relative path in the system bundle
+ url = findInSystemBundle(propJavaProfile);
+ }
+ if (url == null && vmProfile != null) {
+ // look for a profile in the system bundle based on the vm profile
+ String javaProfile = vmProfile + PROFILE_EXT;
+ url = findInSystemBundle(javaProfile);
+ if (url == null)
+ url = getNextBestProfile(javaEdition, javaVersion);
+ }
+ if (url == null)
+ // the profile url is still null then use the osgi min profile in OSGi by default
+ url = findInSystemBundle("OSGi_Minimum-1.2.profile"); //$NON-NLS-1$
+ if (url != null) {
+ InputStream in = null;
+ try {
+ in = url.openStream();
+ result.load(new BufferedInputStream(in));
+ } catch (IOException e) {
+ // TODO consider logging ...
+ } finally {
+ if (in != null)
+ try {
+ in.close();
+ } catch (IOException ee) {
+ // do nothing
+ }
+ }
+ }
+ // set the profile name if it does not provide one
+ if (result.getProperty(Constants.OSGI_JAVA_PROFILE_NAME) == null)
+ if (vmProfile != null)
+ result.put(Constants.OSGI_JAVA_PROFILE_NAME, vmProfile.replace('_', '/'));
+ else
+ // last resort; default to the absolute minimum profile name for the framework
+ result.put(Constants.OSGI_JAVA_PROFILE_NAME, "OSGi/Minimum-1.2"); //$NON-NLS-1$
+ return result;
+ }
+
+ private URL getNextBestProfile(String javaEdition, Version javaVersion) {
+ if (javaVersion == null || (javaEdition != J2SE && javaEdition != JAVASE))
+ return null; // we cannot automatically choose the next best profile unless this is a J2SE or JavaSE vm
+ URL bestProfile = findNextBestProfile(javaEdition, javaVersion);
+ if (bestProfile == null && javaEdition == JAVASE)
+ // if this is a JavaSE VM then search for a lower J2SE profile
+ bestProfile = findNextBestProfile(J2SE, javaVersion);
+ return bestProfile;
+ }
+
+ private URL findNextBestProfile(String javaEdition, Version javaVersion) {
+ URL result = null;
+ int minor = javaVersion.getMinor();
+ do {
+ result = findInSystemBundle(javaEdition + javaVersion.getMajor() + "." + minor + PROFILE_EXT); //$NON-NLS-1$
+ minor = minor - 1;
+ } while (result == null && minor > 0);
+ return result;
+ }
+
+ private URL findInSystemBundle(String entry) {
+ URL result = systemBundle.getEntry0(entry);
+ if (result == null) {
+ // Check the ClassLoader in case we're launched off the Java boot classpath
+ ClassLoader loader = getClass().getClassLoader();
+ result = loader == null ? ClassLoader.getSystemResource(entry) : loader.getResource(entry);
+ }
+ return result;
+ }
+
+ /**
+ * This method return the state of the framework.
+ *
+ */
+ protected boolean isActive() {
+ return (active);
+ }
+
+ /**
+ * This method is called to destory the framework instance.
+ *
+ */
+ public synchronized void close() {
+ if (adaptor == null)
+ return;
+ if (active)
+ shutdown(FrameworkEvent.STOPPED);
+
+ synchronized (bundles) {
+ List<AbstractBundle> allBundles = bundles.getBundles();
+ int size = allBundles.size();
+ for (int i = 0; i < size; i++) {
+ AbstractBundle bundle = allBundles.get(i);
+ bundle.close();
+ }
+ bundles.removeAllBundles();
+ }
+ serviceRegistry = null;
+ allBundleListeners.clear();
+ allSyncBundleListeners.clear();
+ allFrameworkListeners.clear();
+ if (eventManager != null) {
+ eventManager.close();
+ eventManager = null;
+ }
+ secureAction = null;
+ packageAdmin = null;
+ adaptor = null;
+ uninstallURLStreamHandlerFactory();
+ uninstallContentHandlerFactory();
+ if (System.getSecurityManager() instanceof EquinoxSecurityManager)
+ System.setSecurityManager(null);
+ }
+
+ /**
+ * Start the framework.
+ *
+ * When the framework is started. The following actions occur: 1. Event
+ * handling is enabled. Events can now be delivered to listeners. 2. All
+ * bundles which are recorded as started are started as described in the
+ * Bundle.start() method. These bundles are the bundles that were started
+ * when the framework was last stopped. Reports any exceptions that occur
+ * during startup using FrameworkEvents. 3. A FrameworkEvent of type
+ * FrameworkEvent.STARTED is broadcast.
+ *
+ */
+ public synchronized void launch() {
+ /* Return if framework already started */
+ if (active) {
+ return;
+ }
+ /* mark framework as started */
+ active = true;
+ shutdownEvent = new FrameworkEvent[1];
+ if (THREAD_NORMAL.equals(FrameworkProperties.getProperty(PROP_FRAMEWORK_THREAD, THREAD_NORMAL))) {
+ Thread fwkThread = new Thread(this, "Framework Active Thread"); //$NON-NLS-1$
+ fwkThread.setDaemon(false);
+ fwkThread.start();
+ }
+ /* Resume systembundle */
+ if (Debug.DEBUG_GENERAL) {
+ Debug.println("Trying to launch framework"); //$NON-NLS-1$
+ }
+ systemBundle.resume();
+ signedContentFactory = new ServiceTracker<SignedContentFactory, SignedContentFactory>(systemBundle.getBundleContext(), SignedContentFactory.class.getName(), null);
+ signedContentFactory.open();
+ }
+
+ /**
+ * Stop the framework.
+ *
+ * When the framework is stopped. The following actions occur: 1. Suspend
+ * all started bundles as described in the Bundle.stop method except that
+ * the bundle is recorded as started. These bundles will be restarted when
+ * the framework is next started. Reports any exceptions that occur during
+ * stopping using FrameworkEvents. 2. Event handling is disabled.
+ *
+ */
+ public synchronized void shutdown(int eventType) {
+ /* Return if framework already stopped */
+ if (!active)
+ return;
+ this.shutdownEvent[0] = new FrameworkEvent(eventType, systemBundle, null);
+ /*
+ * set the state of the System Bundle to STOPPING.
+ * this must be done first according to section 4.19.2 from the OSGi R3 spec.
+ */
+ systemBundle.state = Bundle.STOPPING;
+ publishBundleEvent(BundleEvent.STOPPING, systemBundle); // need to send system bundle stopping event
+ /* call the FrameworkAdaptor.frameworkStopping method first */
+ try {
+ adaptor.frameworkStopping(systemBundle.getContext());
+ } catch (Throwable t) {
+ publishFrameworkEvent(FrameworkEvent.ERROR, systemBundle, t);
+ }
+ /* Suspend systembundle */
+ if (Debug.DEBUG_GENERAL) {
+ Debug.println("Trying to shutdown Framework"); //$NON-NLS-1$
+ }
+ systemBundle.suspend();
+ try {
+ adaptor.compactStorage();
+ } catch (IOException e) {
+ publishFrameworkEvent(FrameworkEvent.ERROR, systemBundle, e);
+ }
+ if (signedContentFactory != null)
+ signedContentFactory.close();
+ /* mark framework as stopped */
+ active = false;
+ notifyAll();
+ }
+
+ /**
+ * Create a new Bundle object.
+ * @param bundledata the BundleData of the Bundle to create
+ */
+ AbstractBundle createAndVerifyBundle(int operationType, Bundle target, BundleData bundledata, boolean setBundle) throws BundleException {
+ // Check for a bundle already installed with the same symbolic name and version.
+ if (BSN_VERSION != BSN_VERSION_MULTIPLE && bundledata.getSymbolicName() != null) {
+ List<AbstractBundle> installedBundles = getBundleBySymbolicName(bundledata.getSymbolicName(), bundledata.getVersion());
+ if (operationType == CollisionHook.UPDATING) {
+ installedBundles.remove(target);
+ }
+ if (BSN_VERSION == BSN_VERSION_MANAGED && !installedBundles.isEmpty()) {
+ notifyCollisionHooks(operationType, target, installedBundles);
+ }
+ if (!installedBundles.isEmpty()) {
+ Bundle installedBundle = installedBundles.iterator().next();
+ String msg = NLS.bind(Msg.BUNDLE_INSTALL_SAME_UNIQUEID, new Object[] {installedBundle.getSymbolicName(), installedBundle.getVersion().toString(), installedBundle.getLocation()});
+ throw new DuplicateBundleException(msg, installedBundle);
+ }
+ }
+ return AbstractBundle.createBundle(bundledata, this, setBundle);
+ }
+
+ private class DuplicateBundleException extends BundleException implements StatusException {
+ private static final long serialVersionUID = 135669822846323624L;
+ private transient Bundle duplicate;
+
+ public DuplicateBundleException(String msg, Bundle duplicate) {
+ super(msg, BundleException.DUPLICATE_BUNDLE_ERROR);
+ this.duplicate = duplicate;
+ }
+
+ public Object getStatus() {
+ return duplicate;
+ }
+
+ public int getStatusCode() {
+ return StatusException.CODE_OK;
+ }
+
+ }
+
+ /**
+ * Retrieve the value of the named environment property. Values are
+ * provided for the following properties:
+ * <dl>
+ * <dt><code>org.osgi.framework.version</code>
+ * <dd>The version of the framework.
+ * <dt><code>org.osgi.framework.vendor</code>
+ * <dd>The vendor of this framework implementation.
+ * <dt><code>org.osgi.framework.language</code>
+ * <dd>The language being used. See ISO 639 for possible values.
+ * <dt><code>org.osgi.framework.os.name</code>
+ * <dd>The name of the operating system of the hosting computer.
+ * <dt><code>org.osgi.framework.os.version</code>
+ * <dd>The version number of the operating system of the hosting computer.
+ * <dt><code>org.osgi.framework.processor</code>
+ * <dd>The name of the processor of the hosting computer.
+ * </dl>
+ *
+ * <p>
+ * Note: These last four properties are used by the <code>Bundle-NativeCode</code>
+ * manifest header's matching algorithm for selecting native code.
+ *
+ * @param key
+ * The name of the requested property.
+ * @return The value of the requested property, or <code>null</code> if
+ * the property is undefined.
+ */
+ public String getProperty(String key) {
+ return properties.getProperty(key);
+ }
+
+ /**
+ * Retrieve the value of the named environment property. Values are
+ * provided for the following properties:
+ * <dl>
+ * <dt><code>org.osgi.framework.version</code>
+ * <dd>The version of the framework.
+ * <dt><code>org.osgi.framework.vendor</code>
+ * <dd>The vendor of this framework implementation.
+ * <dt><code>org.osgi.framework.language</code>
+ * <dd>The language being used. See ISO 639 for possible values.
+ * <dt><code>org.osgi.framework.os.name</code>
+ * <dd>The name of the operating system of the hosting computer.
+ * <dt><code>org.osgi.framework.os.version</code>
+ * <dd>The version number of the operating system of the hosting computer.
+ * <dt><code>org.osgi.framework.processor</code>
+ * <dd>The name of the processor of the hosting computer.
+ * </dl>
+ *
+ * <p>
+ * Note: These last four properties are used by the <code>Bundle-NativeCode</code>
+ * manifest header's matching algorithm for selecting native code.
+ *
+ * @param key
+ * The name of the requested property.
+ * @param def
+ * A default value is the requested property is not present.
+ * @return The value of the requested property, or the default value if the
+ * property is undefined.
+ */
+ protected String getProperty(String key, String def) {
+ return properties.getProperty(key, def);
+ }
+
+ /**
+ * Set a system property.
+ *
+ * @param key
+ * The name of the property to set.
+ * @param value
+ * The value to set.
+ * @return The previous value of the property or null if the property was
+ * not previously set.
+ */
+ protected Object setProperty(String key, String value) {
+ return properties.put(key, value);
+ }
+
+ /**
+ * Install a bundle from an InputStream.
+ *
+ * @param location
+ * The location identifier of the bundle to install.
+ * @param in
+ * The InputStream from which the bundle will be read. If null
+ * then the location is used to get the bundle content.
+ * @return The Bundle of the installed bundle.
+ */
+ AbstractBundle installBundle(final String location, final InputStream in, final BundleContextImpl origin) throws BundleException {
+ if (Debug.DEBUG_GENERAL) {
+ Debug.println("install from inputstream: " + location + ", " + in); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ final AccessControlContext callerContext = AccessController.getContext();
+ return installWorker(location, new PrivilegedExceptionAction<AbstractBundle>() {
+ public AbstractBundle run() throws BundleException {
+ /* Map the InputStream or location to a URLConnection */
+ URLConnection source = in != null ? new BundleSource(in) : adaptor.mapLocationToURLConnection(location);
+ /* call the worker to install the bundle */
+ return installWorkerPrivileged(location, source, callerContext, origin);
+ }
+ }, origin);
+ }
+
+ /**
+ * Worker method to install a bundle. It obtains the reservation for the
+ * location and calls the specified action.
+ *
+ * @param location
+ * The location identifier of the bundle to install.
+ * @param action
+ * A PrivilegedExceptionAction which calls the real worker.
+ * @return The {@link AbstractBundle}of the installed bundle.
+ * @exception BundleException
+ * If the action throws an error.
+ */
+ protected AbstractBundle installWorker(String location, PrivilegedExceptionAction<AbstractBundle> action, BundleContext origin) throws BundleException {
+ synchronized (installLock) {
+ while (true) {
+ /* Check that the bundle is not already installed. */
+ AbstractBundle bundle = getBundleByLocation(location);
+ /* If already installed, return bundle object */
+ if (bundle != null) {
+ Bundle visible = origin.getBundle(bundle.getBundleId());
+ if (visible == null) {
+ BundleData data = bundle.getBundleData();
+ String msg = NLS.bind(Msg.BUNDLE_INSTALL_SAME_UNIQUEID, new Object[] {data.getSymbolicName(), data.getVersion().toString(), data.getLocation()});
+ throw new BundleException(msg, BundleException.REJECTED_BY_HOOK);
+ }
+ return bundle;
+ }
+ Thread current = Thread.currentThread();
+ /* Check for and make reservation */
+ Thread reservation = installLock.put(location, current);
+ /* if the location is not already reserved */
+ if (reservation == null) {
+ /* we have made the reservation and can continue */
+ break;
+ }
+ /* the location was already reserved */
+ /*
+ * If the reservation is held by the current thread, we have
+ * recursed to install the same bundle!
+ */
+ if (current.equals(reservation)) {
+ throw new BundleException(Msg.BUNDLE_INSTALL_RECURSION_EXCEPTION, BundleException.STATECHANGE_ERROR);
+ }
+ try {
+ /* wait for the reservation to be released */
+ installLock.wait();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new BundleException("Thread has been interrupted while waiting for the location lock.", e); //$NON-NLS-1$
+ }
+ }
+ }
+ /* Don't call adaptor while holding the install lock */
+ try {
+ AbstractBundle bundle = AccessController.doPrivileged(action);
+ publishBundleEvent(new BundleEvent(BundleEvent.INSTALLED, bundle, origin.getBundle()));
+ return bundle;
+ } catch (PrivilegedActionException e) {
+ if (e.getException() instanceof RuntimeException)
+ throw (RuntimeException) e.getException();
+ throw (BundleException) e.getException();
+ } finally {
+ synchronized (installLock) {
+ /* release reservation */
+ installLock.remove(location);
+ /* wake up all waiters */
+ installLock.notifyAll();
+ }
+ }
+ }
+
+ /**
+ * Worker method to install a bundle. It calls the FrameworkAdaptor object
+ * to install the bundle in persistent storage.
+ *
+ * @param location
+ * The location identifier of the bundle to install.
+ * @param source
+ * The URLConnection from which the bundle will be read.
+ * @param callerContext
+ * The caller access control context
+ * @param origin
+ * The origin bundle context that is installing the the bundle
+ * @return The {@link AbstractBundle}of the installed bundle.
+ * @exception BundleException
+ * If the provided stream cannot be read.
+ */
+ protected AbstractBundle installWorkerPrivileged(String location, URLConnection source, AccessControlContext callerContext, BundleContextImpl origin) throws BundleException {
+ BundleOperation storage = adaptor.installBundle(location, source);
+ final AbstractBundle bundle;
+ try {
+ BundleData bundledata = storage.begin();
+ bundle = createAndVerifyBundle(CollisionHook.INSTALLING, origin.getBundle(), bundledata, true);
+
+ BundleWatcher bundleStats = adaptor.getBundleWatcher();
+ if (bundleStats != null)
+ bundleStats.watchBundle(bundle, BundleWatcher.START_INSTALLING);
+
+ try {
+ bundle.load();
+ if (System.getSecurityManager() != null) {
+ final boolean extension = (bundledata.getType() & (BundleData.TYPE_BOOTCLASSPATH_EXTENSION | BundleData.TYPE_FRAMEWORK_EXTENSION | BundleData.TYPE_EXTCLASSPATH_EXTENSION)) != 0;
+ // must check for AllPermission before allow a bundle extension to be installed
+ if (extension && !bundle.hasPermission(new AllPermission()))
+ throw new BundleException(Msg.BUNDLE_EXTENSION_PERMISSION, BundleException.SECURITY_ERROR, new SecurityException(Msg.BUNDLE_EXTENSION_PERMISSION));
+ try {
+ AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
+ public Object run() throws Exception {
+ checkAdminPermission(bundle, AdminPermission.LIFECYCLE);
+ if (extension) // need special permission to install extension bundles
+ checkAdminPermission(bundle, AdminPermission.EXTENSIONLIFECYCLE);
+ return null;
+ }
+ }, callerContext);
+ } catch (PrivilegedActionException e) {
+ throw e.getException();
+ }
+ }
+ // must add the bundle before committing (bug 330905)
+ bundles.add(bundle);
+ storage.commit(false);
+ } catch (Throwable error) {
+ bundles.remove(bundle);
+ synchronized (bundles) {
+ bundle.unload();
+ }
+ bundle.close();
+ throw error;
+ } finally {
+ if (bundleStats != null)
+ bundleStats.watchBundle(bundle, BundleWatcher.END_INSTALLING);
+ }
+
+ } catch (Throwable t) {
+ try {
+ storage.undo();
+ } catch (BundleException ee) {
+ publishFrameworkEvent(FrameworkEvent.ERROR, systemBundle, ee);
+ }
+ if (t instanceof SecurityException)
+ throw (SecurityException) t;
+ if (t instanceof BundleException)
+ throw (BundleException) t;
+ throw new BundleException(t.getMessage(), t);
+ }
+ return bundle;
+ }
+
+ /**
+ * Retrieve the bundle that has the given unique identifier.
+ *
+ * @param id
+ * The identifier of the bundle to retrieve.
+ * @return A {@link AbstractBundle}object, or <code>null</code> if the
+ * identifier doesn't match any installed bundle.
+ */
+ // changed visibility to gain access through the adaptor
+ public AbstractBundle getBundle(long id) {
+ synchronized (bundles) {
+ return bundles.getBundle(id);
+ }
+ }
+
+ AbstractBundle getBundle(final BundleContextImpl context, long id) {
+ AbstractBundle bundle = getBundle(id);
+ // TODO we make the system bundle special because there are lots of places
+ // where we assume the system bundle can get all bundles
+ if (bundle == null || context.getBundle().getBundleId() == 0)
+ return bundle;
+ List<AbstractBundle> single = new ArrayList<AbstractBundle>(1);
+ single.add(bundle);
+ notifyFindHooks(context, single);
+ return single.size() == 0 ? null : bundle;
+ }
+
+ public BundleContextImpl getSystemBundleContext() {
+ if (systemBundle == null)
+ return null;
+ return systemBundle.context;
+ }
+
+ public PackageAdminImpl getPackageAdmin() {
+ return packageAdmin;
+ }
+
+ /**
+ * Retrieve the bundles that has the given symbolic name and version.
+ *
+ * @param symbolicName
+ * The symbolic name of the bundle to retrieve
+ * @param version The version of the bundle to retrieve
+ * @return A collection of {@link AbstractBundle} that match the symbolic name and version
+ */
+ public List<AbstractBundle> getBundleBySymbolicName(String symbolicName, Version version) {
+ synchronized (bundles) {
+ return bundles.getBundles(symbolicName, version);
+ }
+ }
+
+ /**
+ * Retrieve the BundleRepository of all installed bundles. The list is
+ * valid at the time of the call to getBundles, but the framework is a very
+ * dynamic environment and bundles can be installed or uninstalled at
+ * anytime.
+ *
+ * @return The BundleRepository.
+ */
+ protected BundleRepository getBundles() {
+ return (bundles);
+ }
+
+ /**
+ * Retrieve a list of all installed bundles. The list is valid at the time
+ * of the call to getBundleAlls, but the framework is a very dynamic
+ * environment and bundles can be installed or uninstalled at anytime.
+ *
+ * @return An Array of {@link AbstractBundle}objects, one object per installed
+ * bundle.
+ */
+ protected AbstractBundle[] getAllBundles() {
+ synchronized (bundles) {
+ List<AbstractBundle> allBundles = bundles.getBundles();
+ int size = allBundles.size();
+ if (size == 0) {
+ return (null);
+ }
+ AbstractBundle[] bundlelist = new AbstractBundle[size];
+ allBundles.toArray(bundlelist);
+ return (bundlelist);
+ }
+ }
+
+ AbstractBundle[] getBundles(final BundleContextImpl context) {
+ List<AbstractBundle> allBundles;
+ synchronized (bundles) {
+ allBundles = new ArrayList<AbstractBundle>(bundles.getBundles());
+ }
+ notifyFindHooks(context, allBundles);
+ return allBundles.toArray(new AbstractBundle[allBundles.size()]);
+ }
+
+ private void notifyFindHooks(final BundleContextImpl context, List<AbstractBundle> allBundles) {
+ final Collection<Bundle> shrinkable = new ShrinkableCollection<Bundle>(allBundles);
+ if (System.getSecurityManager() == null) {
+ notifyFindHooksPriviledged(context, shrinkable);
+ } else {
+ AccessController.doPrivileged(new PrivilegedAction<Object>() {
+ public Object run() {
+ notifyFindHooksPriviledged(context, shrinkable);
+ return null;
+ }
+ });
+ }
+ }
+
+ void notifyFindHooksPriviledged(final BundleContextImpl context, final Collection<Bundle> allBundles) {
+ if (Debug.DEBUG_HOOKS) {
+ Debug.println("notifyBundleFindHooks(" + allBundles + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ getServiceRegistry().notifyHooksPrivileged(new HookContext() {
+ public void call(Object hook, ServiceRegistration<?> hookRegistration) throws Exception {
+ if (hook instanceof FindHook) {
+ ((FindHook) hook).find(context, allBundles);
+ }
+ }
+
+ public String getHookClassName() {
+ return findHookName;
+ }
+
+ public String getHookMethodName() {
+ return "find"; //$NON-NLS-1$
+ }
+ });
+ }
+
+ private void notifyCollisionHooks(final int operationType, final Bundle target, List<AbstractBundle> collisionCandidates) {
+ final Collection<Bundle> shrinkable = new ShrinkableCollection<Bundle>(collisionCandidates);
+ if (System.getSecurityManager() == null) {
+ notifyCollisionHooksPriviledged(operationType, target, shrinkable);
+ } else {
+ AccessController.doPrivileged(new PrivilegedAction<Object>() {
+ public Object run() {
+ notifyCollisionHooksPriviledged(operationType, target, shrinkable);
+ return null;
+ }
+ });
+ }
+ }
+
+ void notifyCollisionHooksPriviledged(final int operationType, final Bundle target, final Collection<Bundle> collisionCandidates) {
+ if (Debug.DEBUG_HOOKS) {
+ Debug.println("notifyCollisionHooks(" + operationType + ", " + target + ", " + collisionCandidates + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ }
+ getServiceRegistry().notifyHooksPrivileged(new HookContext() {
+ public void call(Object hook, ServiceRegistration<?> hookRegistration) throws Exception {
+ if (hook instanceof CollisionHook) {
+ ((CollisionHook) hook).filterCollisions(operationType, target, collisionCandidates);
+ }
+ }
+
+ public String getHookClassName() {
+ return collisionHookName;
+ }
+
+ public String getHookMethodName() {
+ return "filterCollisions"; //$NON-NLS-1$
+ }
+ });
+ }
+
+ /**
+ * Resume a bundle.
+ *
+ * @param bundle
+ * Bundle to resume.
+ */
+ protected void resumeBundle(AbstractBundle bundle) {
+ if (bundle.isActive()) {
+ // if bundle is active.
+ return;
+ }
+ try {
+ if (Debug.DEBUG_GENERAL) {
+ Debug.println("Trying to resume bundle " + bundle); //$NON-NLS-1$
+ }
+ bundle.resume();
+ } catch (BundleException be) {
+ if (Debug.DEBUG_GENERAL) {
+ Debug.println("Bundle resume exception: " + be.getMessage()); //$NON-NLS-1$
+ Debug.printStackTrace(be.getNestedException() == null ? be : be.getNestedException());
+ }
+ publishFrameworkEvent(FrameworkEvent.ERROR, bundle, be);
+ }
+ }
+
+ /**
+ * Suspend a bundle.
+ *
+ * @param bundle
+ * Bundle to suspend.
+ * @param lock
+ * true if state change lock should be held when returning from
+ * this method.
+ * @return true if bundle was active and is now suspended.
+ */
+ protected boolean suspendBundle(AbstractBundle bundle, boolean lock) {
+ boolean changed = false;
+ if (!bundle.isActive() || bundle.isFragment()) {
+ // if bundle is not active or is a fragment then do nothing.
+ return changed;
+ }
+ try {
+ if (Debug.DEBUG_GENERAL) {
+ Debug.println("Trying to suspend bundle " + bundle); //$NON-NLS-1$
+ }
+ bundle.suspend(lock);
+ } catch (BundleException be) {
+ if (Debug.DEBUG_GENERAL) {
+ Debug.println("Bundle suspend exception: " + be.getMessage()); //$NON-NLS-1$
+ Debug.printStackTrace(be.getNestedException() == null ? be : be.getNestedException());
+ }
+ publishFrameworkEvent(FrameworkEvent.ERROR, bundle, be);
+ }
+ if (!bundle.isActive()) {
+ changed = true;
+ }
+ return (changed);
+ }
+
+ /**
+ * Locate an installed bundle with a given identity.
+ *
+ * @param location
+ * string for the bundle
+ * @return Bundle object for bundle with the specified location or null if
+ * no bundle is installed with the specified location.
+ */
+ protected AbstractBundle getBundleByLocation(String location) {
+ synchronized (bundles) {
+ // this is not optimized; do not think it will get called
+ // that much.
+ final String finalLocation = location;
+
+ //Bundle.getLocation requires AdminPermission (metadata)
+ return AccessController.doPrivileged(new PrivilegedAction<AbstractBundle>() {
+ public AbstractBundle run() {
+ List<AbstractBundle> allBundles = bundles.getBundles();
+ int size = allBundles.size();
+ for (int i = 0; i < size; i++) {
+ AbstractBundle bundle = allBundles.get(i);
+ if (finalLocation.equals(bundle.getLocation())) {
+ return bundle;
+ }
+ }
+ return null;
+ }
+ });
+ }
+ }
+
+ /**
+ * Locate an installed bundle with a given symbolic name
+ *
+ * @param symbolicName
+ * The symbolic name for the bundle
+ * @return Bundle object for bundle with the specified Unique or null if no
+ * bundle is installed with the specified symbolicName.
+ */
+ protected AbstractBundle[] getBundleBySymbolicName(String symbolicName) {
+ synchronized (bundles) {
+ return bundles.getBundles(symbolicName);
+ }
+ }
+
+ /**
+ * Creates a <code>File</code> object for a file in the persistent
+ * storage area provided for the bundle by the framework. If the adaptor
+ * does not have file system support, this method will return <code>null</code>.
+ *
+ * <p>
+ * A <code>File</code> object for the base directory of the persistent
+ * storage area provided for the context bundle by the framework can be
+ * obtained by calling this method with the empty string ("") as the
+ * parameter.
+ */
+ protected File getDataFile(final AbstractBundle bundle, final String filename) {
+ return AccessController.doPrivileged(new GetDataFileAction(bundle, filename));
+ }
+
+ /**
+ * Check for specific AdminPermission (RFC 73)
+ */
+ protected void checkAdminPermission(Bundle bundle, String action) {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null)
+ sm.checkPermission(getAdminPermission(bundle, action));
+ }
+
+ // gets AdminPermission objects from a cache to reduce the number of AdminPermission
+ // objects that are created.
+ private AdminPermission getAdminPermission(Bundle bundle, String action) {
+ synchronized (adminPermissions) {
+ Long ID = new Long(bundle.getBundleId());
+ Map<String, AdminPermission> bundlePermissions = adminPermissions.get(ID);
+ if (bundlePermissions == null) {
+ bundlePermissions = new HashMap<String, AdminPermission>();
+ adminPermissions.put(ID, bundlePermissions);
+ }
+ AdminPermission result = bundlePermissions.get(action);
+ if (result == null) {
+ result = new AdminPermission(bundle, action);
+ bundlePermissions.put(action, result);
+ }
+ return result;
+ }
+ }
+
+ /**
+ * This is necessary for running from a JXE, otherwise the SecurityManager
+ * is set much later than we would like!
+ */
+ protected void installSecurityManager() {
+ String securityManager = FrameworkProperties.getProperty(Constants.FRAMEWORK_SECURITY, FrameworkProperties.getProperty(PROP_EQUINOX_SECURITY, FrameworkProperties.getProperty("java.security.manager")));
+ if (securityManager != null) {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm == null) {
+ if (securityManager.length() == 0)
+ sm = new SecurityManager(); // use the default one from java
+ else if (securityManager.equals(SECURITY_OSGI))
+ sm = new EquinoxSecurityManager(); // use an OSGi enabled manager that understands postponed conditions
+ else {
+ // try to use a specific classloader by classname
+ try {
+ Class<?> clazz = Class.forName(securityManager);
+ sm = (SecurityManager) clazz.newInstance();
+ } catch (ClassNotFoundException e) {
+ // do nothing
+ } catch (ClassCastException e) {
+ // do nothing
+ } catch (InstantiationException e) {
+ // do nothing
+ } catch (IllegalAccessException e) {
+ // do nothing
+ }
+ }
+ if (sm == null)
+ throw new NoClassDefFoundError(securityManager);
+ if (Debug.DEBUG_SECURITY)
+ Debug.println("Setting SecurityManager to: " + sm); //$NON-NLS-1$
+ System.setSecurityManager(sm);
+ return;
+ }
+ }
+ }
+
+ void addFrameworkListener(FrameworkListener listener, BundleContextImpl context) {
+ synchronized (allFrameworkListeners) {
+ CopyOnWriteIdentityMap<FrameworkListener, FrameworkListener> listeners = allFrameworkListeners.get(context);
+ if (listeners == null) {
+ listeners = new CopyOnWriteIdentityMap<FrameworkListener, FrameworkListener>();
+ allFrameworkListeners.put(context, listeners);
+ }
+ listeners.put(listener, listener);
+ }
+ }
+
+ void removeFrameworkListener(FrameworkListener listener, BundleContextImpl context) {
+ synchronized (allFrameworkListeners) {
+ CopyOnWriteIdentityMap<FrameworkListener, FrameworkListener> listeners = allFrameworkListeners.get(context);
+ if (listeners != null)
+ listeners.remove(listener);
+ }
+ }
+
+ void removeAllListeners(BundleContextImpl context) {
+ synchronized (allBundleListeners) {
+ allBundleListeners.remove(context);
+ }
+ synchronized (allSyncBundleListeners) {
+ allSyncBundleListeners.remove(context);
+ }
+ synchronized (allFrameworkListeners) {
+ allFrameworkListeners.remove(context);
+ }
+ }
+
+ /**
+ * Deliver a FrameworkEvent.
+ *
+ * @param type
+ * FrameworkEvent type.
+ * @param bundle
+ * Affected bundle or null for system bundle.
+ * @param throwable
+ * Related exception or null.
+ */
+ public void publishFrameworkEvent(int type, Bundle bundle, Throwable throwable) {
+ publishFrameworkEvent(type, bundle, throwable, (FrameworkListener[]) null);
+ }
+
+ public void publishFrameworkEvent(int type, Bundle bundle, Throwable throwable, final FrameworkListener... listeners) {
+ if (bundle == null)
+ bundle = systemBundle;
+ final FrameworkEvent event = new FrameworkEvent(type, bundle, throwable);
+ if (System.getSecurityManager() == null) {
+ publishFrameworkEventPrivileged(event, listeners);
+ } else {
+ AccessController.doPrivileged(new PrivilegedAction<Object>() {
+ public Object run() {
+ publishFrameworkEventPrivileged(event, listeners);
+ return null;
+ }
+ });
+ }
+ }
+
+ public void publishFrameworkEventPrivileged(FrameworkEvent event, FrameworkListener... callerListeners) {
+ // Build the listener snapshot
+ Map<BundleContextImpl, Set<Map.Entry<FrameworkListener, FrameworkListener>>> listenerSnapshot;
+ synchronized (allFrameworkListeners) {
+ listenerSnapshot = new HashMap<BundleContextImpl, Set<Map.Entry<FrameworkListener, FrameworkListener>>>(allFrameworkListeners.size());
+ for (Map.Entry<BundleContextImpl, CopyOnWriteIdentityMap<FrameworkListener, FrameworkListener>> entry : allFrameworkListeners.entrySet()) {
+ CopyOnWriteIdentityMap<FrameworkListener, FrameworkListener> listeners = entry.getValue();
+ if (!listeners.isEmpty()) {
+ listenerSnapshot.put(entry.getKey(), listeners.entrySet());
+ }
+ }
+ }
+ // If framework event hook were defined they would be called here
+
+ // deliver the event to the snapshot
+ ListenerQueue<FrameworkListener, FrameworkListener, FrameworkEvent> queue = newListenerQueue();
+
+ // add the listeners specified by the caller first
+ if (callerListeners != null && callerListeners.length > 0) {
+ Map<FrameworkListener, FrameworkListener> listeners = new HashMap<FrameworkListener, FrameworkListener>();
+ for (FrameworkListener listener : callerListeners) {
+ if (listener != null)
+ listeners.put(listener, listener);
+ }
+ // We use the system bundle context as the dispatcher
+ if (listeners.size() > 0) {
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ EventDispatcher<FrameworkListener, FrameworkListener, FrameworkEvent> dispatcher = (EventDispatcher) getSystemBundleContext();
+ queue.queueListeners(listeners.entrySet(), dispatcher);
+ }
+ }
+
+ for (Map.Entry<BundleContextImpl, Set<Map.Entry<FrameworkListener, FrameworkListener>>> entry : listenerSnapshot.entrySet()) {
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ EventDispatcher<FrameworkListener, FrameworkListener, FrameworkEvent> dispatcher = (EventDispatcher) entry.getKey();
+ Set<Map.Entry<FrameworkListener, FrameworkListener>> listeners = entry.getValue();
+ queue.queueListeners(listeners, dispatcher);
+ }
+
+ queue.dispatchEventAsynchronous(FRAMEWORKEVENT, event);
+ }
+
+ void addBundleListener(BundleListener listener, BundleContextImpl context) {
+ if (listener instanceof SynchronousBundleListener) {
+ checkAdminPermission(context.getBundle(), AdminPermission.LISTENER);
+ synchronized (allSyncBundleListeners) {
+ CopyOnWriteIdentityMap<SynchronousBundleListener, SynchronousBundleListener> listeners = allSyncBundleListeners.get(context);
+ if (listeners == null) {
+ listeners = new CopyOnWriteIdentityMap<SynchronousBundleListener, SynchronousBundleListener>();
+ allSyncBundleListeners.put(context, listeners);
+ }
+ listeners.put((SynchronousBundleListener) listener, (SynchronousBundleListener) listener);
+ }
+ } else {
+ synchronized (allBundleListeners) {
+ CopyOnWriteIdentityMap<BundleListener, BundleListener> listeners = allBundleListeners.get(context);
+ if (listeners == null) {
+ listeners = new CopyOnWriteIdentityMap<BundleListener, BundleListener>();
+ allBundleListeners.put(context, listeners);
+ }
+ listeners.put(listener, listener);
+ }
+ }
+ }
+
+ void removeBundleListener(BundleListener listener, BundleContextImpl context) {
+ if (listener instanceof SynchronousBundleListener) {
+ checkAdminPermission(context.getBundle(), AdminPermission.LISTENER);
+ synchronized (allSyncBundleListeners) {
+ CopyOnWriteIdentityMap<SynchronousBundleListener, SynchronousBundleListener> listeners = allSyncBundleListeners.get(context);
+ if (listeners != null)
+ listeners.remove(listener);
+ }
+ } else {
+ synchronized (allBundleListeners) {
+ CopyOnWriteIdentityMap<BundleListener, BundleListener> listeners = allBundleListeners.get(context);
+ if (listeners != null)
+ listeners.remove(listener);
+ }
+ }
+ }
+
+ /**
+ * Deliver a BundleEvent to SynchronousBundleListeners (synchronous). and
+ * BundleListeners (asynchronous).
+ *
+ * @param type
+ * BundleEvent type.
+ * @param bundle
+ * Affected bundle or null.
+ */
+ public void publishBundleEvent(int type, Bundle bundle) {
+ publishBundleEvent(new BundleEvent(type, bundle));
+ }
+
+ private void publishBundleEvent(final BundleEvent event) {
+ if (System.getSecurityManager() == null) {
+ publishBundleEventPrivileged(event);
+ } else {
+ AccessController.doPrivileged(new PrivilegedAction<Object>() {
+ public Object run() {
+ publishBundleEventPrivileged(event);
+ return null;
+ }
+ });
+ }
+ }
+
+ public void publishBundleEventPrivileged(BundleEvent event) {
+ /*
+ * We must collect the snapshots of the sync and async listeners
+ * BEFORE we dispatch the event.
+ */
+ /* Collect snapshot of SynchronousBundleListeners */
+ /* Build the listener snapshot */
+ Map<BundleContextImpl, Set<Map.Entry<SynchronousBundleListener, SynchronousBundleListener>>> listenersSync;
+ synchronized (allSyncBundleListeners) {
+ listenersSync = new HashMap<BundleContextImpl, Set<Map.Entry<SynchronousBundleListener, SynchronousBundleListener>>>(allSyncBundleListeners.size());
+ for (Map.Entry<BundleContextImpl, CopyOnWriteIdentityMap<SynchronousBundleListener, SynchronousBundleListener>> entry : allSyncBundleListeners.entrySet()) {
+ CopyOnWriteIdentityMap<SynchronousBundleListener, SynchronousBundleListener> listeners = entry.getValue();
+ if (!listeners.isEmpty()) {
+ listenersSync.put(entry.getKey(), listeners.entrySet());
+ }
+ }
+ }
+ /* Collect snapshot of BundleListeners; only if the event is NOT STARTING or STOPPING or LAZY_ACTIVATION */
+ Map<BundleContextImpl, Set<Map.Entry<BundleListener, BundleListener>>> listenersAsync = null;
+ if ((event.getType() & (BundleEvent.STARTING | BundleEvent.STOPPING | BundleEvent.LAZY_ACTIVATION)) == 0) {
+ synchronized (allBundleListeners) {
+ listenersAsync = new HashMap<BundleContextImpl, Set<Map.Entry<BundleListener, BundleListener>>>(allBundleListeners.size());
+ for (Map.Entry<BundleContextImpl, CopyOnWriteIdentityMap<BundleListener, BundleListener>> entry : allBundleListeners.entrySet()) {
+ CopyOnWriteIdentityMap<BundleListener, BundleListener> listeners = entry.getValue();
+ if (!listeners.isEmpty()) {
+ listenersAsync.put(entry.getKey(), listeners.entrySet());
+ }
+ }
+ }
+ }
+
+ /* shrink the snapshot.
+ * keySet returns a Collection which cannot be added to and
+ * removals from that collection will result in removals of the
+ * entry from the snapshot.
+ */
+ Collection<BundleContext> shrinkable;
+ if (listenersAsync == null) {
+ shrinkable = asBundleContexts(listenersSync.keySet());
+ } else {
+ shrinkable = new ShrinkableCollection<BundleContext>(asBundleContexts(listenersSync.keySet()), asBundleContexts(listenersAsync.keySet()));
+ }
+ notifyEventHooksPrivileged(event, shrinkable);
+
+ /* Dispatch the event to the snapshot for sync listeners */
+ if (!listenersSync.isEmpty()) {
+ ListenerQueue<SynchronousBundleListener, SynchronousBundleListener, BundleEvent> queue = newListenerQueue();
+ for (Map.Entry<BundleContextImpl, Set<Map.Entry<SynchronousBundleListener, SynchronousBundleListener>>> entry : listenersSync.entrySet()) {
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ EventDispatcher<SynchronousBundleListener, SynchronousBundleListener, BundleEvent> dispatcher = (EventDispatcher) entry.getKey();
+ Set<Map.Entry<SynchronousBundleListener, SynchronousBundleListener>> listeners = entry.getValue();
+ queue.queueListeners(listeners, dispatcher);
+ }
+ queue.dispatchEventSynchronous(BUNDLEEVENTSYNC, event);
+ }
+
+ /* Dispatch the event to the snapshot for async listeners */
+ if ((listenersAsync != null) && !listenersAsync.isEmpty()) {
+ ListenerQueue<BundleListener, BundleListener, BundleEvent> queue = newListenerQueue();
+ for (Map.Entry<BundleContextImpl, Set<Map.Entry<BundleListener, BundleListener>>> entry : listenersAsync.entrySet()) {
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ EventDispatcher<BundleListener, BundleListener, BundleEvent> dispatcher = (EventDispatcher) entry.getKey();
+ Set<Map.Entry<BundleListener, BundleListener>> listeners = entry.getValue();
+ queue.queueListeners(listeners, dispatcher);
+ }
+ queue.dispatchEventAsynchronous(BUNDLEEVENT, event);
+ }
+ }
+
+ /**
+ * Coerce the generic type of a collection from Collection<BundleContextImpl>
+ * to Collection<BundleContext>
+ * @param c Collection to be coerced.
+ * @return c coerced to Collection<BundleContext>
+ */
+ @SuppressWarnings("unchecked")
+ public static Collection<BundleContext> asBundleContexts(Collection<? extends BundleContext> c) {
+ return (Collection<BundleContext>) c;
+ }
+
+ private void notifyEventHooksPrivileged(final BundleEvent event, final Collection<BundleContext> result) {
+ if (event.getType() == Framework.BATCHEVENT_BEGIN || event.getType() == Framework.BATCHEVENT_END)
+ return; // we don't need to send this event; it is used to book case special listeners
+ if (Debug.DEBUG_HOOKS) {
+ Debug.println("notifyBundleEventHooks(" + event.getType() + ":" + event.getBundle() + ", " + result + " )"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ }
+
+ getServiceRegistry().notifyHooksPrivileged(new HookContext() {
+ public void call(Object hook, ServiceRegistration<?> hookRegistration) throws Exception {
+ if (hook instanceof EventHook) {
+ ((EventHook) hook).event(event, result);
+ }
+ }
+
+ public String getHookClassName() {
+ return eventHookName;
+ }
+
+ public String getHookMethodName() {
+ return "event"; //$NON-NLS-1$
+ }
+ });
+ }
+
+ public <K, V, E> ListenerQueue<K, V, E> newListenerQueue() {
+ return new ListenerQueue<K, V, E>(eventManager);
+ }
+
+ private void initializeContextFinder() {
+ Thread current = Thread.currentThread();
+ try {
+ ClassLoader parent = null;
+ // check property for specified parent
+ String type = FrameworkProperties.getProperty(PROP_CONTEXTCLASSLOADER_PARENT);
+ if (CONTEXTCLASSLOADER_PARENT_APP.equals(type))
+ parent = ClassLoader.getSystemClassLoader();
+ else if (CONTEXTCLASSLOADER_PARENT_BOOT.equals(type))
+ parent = null;
+ else if (CONTEXTCLASSLOADER_PARENT_FWK.equals(type))
+ parent = Framework.class.getClassLoader();
+ else if (CONTEXTCLASSLOADER_PARENT_EXT.equals(type)) {
+ ClassLoader appCL = ClassLoader.getSystemClassLoader();
+ if (appCL != null)
+ parent = appCL.getParent();
+ } else { // default is ccl (null or any other value will use ccl)
+ parent = current.getContextClassLoader();
+ }
+ contextFinder = new ContextFinder(parent);
+ current.setContextClassLoader(contextFinder);
+ return;
+ } catch (Exception e) {
+ FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.INFO, 0, NLS.bind(Msg.CANNOT_SET_CONTEXTFINDER, null), 0, e, null);
+ adaptor.getFrameworkLog().log(entry);
+ }
+
+ }
+
+ public static Field getField(Class<?> clazz, Class<?> type, boolean instance) {
+ Field[] fields = clazz.getDeclaredFields();
+ for (int i = 0; i < fields.length; i++) {
+ boolean isStatic = Modifier.isStatic(fields[i].getModifiers());
+ if (instance != isStatic && fields[i].getType().equals(type)) {
+ fields[i].setAccessible(true);
+ return fields[i];
+ }
+ }
+ return null;
+ }
+
+ private void installContentHandlerFactory(BundleContext context, FrameworkAdaptor frameworkAdaptor) {
+ ContentHandlerFactory chf = new ContentHandlerFactory(context, frameworkAdaptor);
+ try {
+ // first try the standard way
+ URLConnection.setContentHandlerFactory(chf);
+ } catch (Error err) {
+ // ok we failed now use more drastic means to set the factory
+ try {
+ forceContentHandlerFactory(chf);
+ } catch (Exception ex) {
+ // this is unexpected, log the exception and throw the original error
+ adaptor.getFrameworkLog().log(new FrameworkEvent(FrameworkEvent.ERROR, context.getBundle(), ex));
+ throw err;
+ }
+ }
+ contentHandlerFactory = chf;
+ }
+
+ private static void forceContentHandlerFactory(ContentHandlerFactory chf) throws Exception {
+ Field factoryField = getField(URLConnection.class, java.net.ContentHandlerFactory.class, false);
+ if (factoryField == null)
+ throw new Exception("Could not find ContentHandlerFactory field"); //$NON-NLS-1$
+ synchronized (URLConnection.class) {
+ java.net.ContentHandlerFactory factory = (java.net.ContentHandlerFactory) factoryField.get(null);
+ // doing a null check here just in case, but it would be really strange if it was null,
+ // because we failed to set the factory normally!!
+
+ if (factory != null) {
+ try {
+ factory.getClass().getMethod("isMultiplexing", (Class[]) null); //$NON-NLS-1$
+ Method register = factory.getClass().getMethod("register", new Class[] {Object.class}); //$NON-NLS-1$
+ register.invoke(factory, new Object[] {chf});
+ } catch (NoSuchMethodException e) {
+ // current factory does not support multiplexing, ok we'll wrap it
+ chf.setParentFactory(factory);
+ factory = chf;
+ }
+ }
+ // null out the field so that we can successfully call setContentHandlerFactory
+ factoryField.set(null, null);
+ // always attempt to clear the handlers cache
+ // This allows an optomization for the single framework use-case
+ resetContentHandlers();
+ URLConnection.setContentHandlerFactory(factory);
+ }
+ }
+
+ private void uninstallContentHandlerFactory() {
+ try {
+ Field factoryField = getField(URLConnection.class, java.net.ContentHandlerFactory.class, false);
+ if (factoryField == null)
+ return; // oh well, we tried.
+ synchronized (URLConnection.class) {
+ java.net.ContentHandlerFactory factory = (java.net.ContentHandlerFactory) factoryField.get(null);
+
+ if (factory == contentHandlerFactory) {
+ factory = (java.net.ContentHandlerFactory) contentHandlerFactory.designateSuccessor();
+ } else {
+ Method unregister = factory.getClass().getMethod("unregister", new Class[] {Object.class}); //$NON-NLS-1$
+ unregister.invoke(factory, new Object[] {contentHandlerFactory});
+ }
+ // null out the field so that we can successfully call setContentHandlerFactory
+ factoryField.set(null, null);
+ // always attempt to clear the handlers cache
+ // This allows an optomization for the single framework use-case
+ // Note that the call to setContentHandlerFactory below may clear this cache
+ // but we want to be sure to clear it here just incase the parent is null.
+ // In this case the call below would not occur.
+ // Also it appears most java libraries actually do not clear the cache
+ // when setContentHandlerFactory is called, go figure!!
+ resetContentHandlers();
+ if (factory != null)
+ URLConnection.setContentHandlerFactory(factory);
+ }
+ } catch (Exception e) {
+ // ignore and continue closing the framework
+ }
+ }
+
+ private static void resetContentHandlers() throws IllegalAccessException {
+ Field handlersField = getField(URLConnection.class, Hashtable.class, false);
+ if (handlersField != null) {
+ @SuppressWarnings("rawtypes")
+ Hashtable<?, ?> handlers = (Hashtable) handlersField.get(null);
+ if (handlers != null)
+ handlers.clear();
+ }
+ }
+
+ private void installURLStreamHandlerFactory(BundleContext context, FrameworkAdaptor frameworkAdaptor) {
+ StreamHandlerFactory shf = new StreamHandlerFactory(context, frameworkAdaptor);
+ try {
+ // first try the standard way
+ URL.setURLStreamHandlerFactory(shf);
+ } catch (Error err) {
+ try {
+ // ok we failed now use more drastic means to set the factory
+ forceURLStreamHandlerFactory(shf);
+ } catch (Exception ex) {
+ adaptor.getFrameworkLog().log(new FrameworkEvent(FrameworkEvent.ERROR, context.getBundle(), ex));
+ throw err;
+ }
+ }
+ streamHandlerFactory = shf;
+ }
+
+ private static void forceURLStreamHandlerFactory(StreamHandlerFactory shf) throws Exception {
+ Field factoryField = getField(URL.class, URLStreamHandlerFactory.class, false);
+ if (factoryField == null)
+ throw new Exception("Could not find URLStreamHandlerFactory field"); //$NON-NLS-1$
+ // look for a lock to synchronize on
+ Object lock = getURLStreamHandlerFactoryLock();
+ synchronized (lock) {
+ URLStreamHandlerFactory factory = (URLStreamHandlerFactory) factoryField.get(null);
+ // doing a null check here just in case, but it would be really strange if it was null,
+ // because we failed to set the factory normally!!
+ if (factory != null) {
+ try {
+ factory.getClass().getMethod("isMultiplexing", (Class[]) null); //$NON-NLS-1$
+ Method register = factory.getClass().getMethod("register", new Class[] {Object.class}); //$NON-NLS-1$
+ register.invoke(factory, new Object[] {shf});
+ } catch (NoSuchMethodException e) {
+ // current factory does not support multiplexing, ok we'll wrap it
+ shf.setParentFactory(factory);
+ factory = shf;
+ }
+ }
+ factoryField.set(null, null);
+ // always attempt to clear the handlers cache
+ // This allows an optomization for the single framework use-case
+ resetURLStreamHandlers();
+ URL.setURLStreamHandlerFactory(factory);
+ }
+ }
+
+ private void uninstallURLStreamHandlerFactory() {
+ try {
+ Field factoryField = getField(URL.class, URLStreamHandlerFactory.class, false);
+ if (factoryField == null)
+ return; // oh well, we tried
+ Object lock = getURLStreamHandlerFactoryLock();
+ synchronized (lock) {
+ URLStreamHandlerFactory factory = (URLStreamHandlerFactory) factoryField.get(null);
+ if (factory == streamHandlerFactory) {
+ factory = (URLStreamHandlerFactory) streamHandlerFactory.designateSuccessor();
+ } else {
+ Method unregister = factory.getClass().getMethod("unregister", new Class[] {Object.class}); //$NON-NLS-1$
+ unregister.invoke(factory, new Object[] {streamHandlerFactory});
+ }
+ factoryField.set(null, null);
+ // always attempt to clear the handlers cache
+ // This allows an optomization for the single framework use-case
+ // Note that the call to setURLStreamHandlerFactory below may clear this cache
+ // but we want to be sure to clear it here just in case the parent is null.
+ // In this case the call below would not occur.
+ resetURLStreamHandlers();
+ if (factory != null)
+ URL.setURLStreamHandlerFactory(factory);
+ }
+ } catch (Exception e) {
+ // ignore and continue closing the framework
+ }
+ }
+
+ private static Object getURLStreamHandlerFactoryLock() throws IllegalAccessException {
+ Object lock;
+ try {
+ Field streamHandlerLockField = URL.class.getDeclaredField("streamHandlerLock"); //$NON-NLS-1$
+ streamHandlerLockField.setAccessible(true);
+ lock = streamHandlerLockField.get(null);
+ } catch (NoSuchFieldException noField) {
+ // could not find the lock, lets sync on the class object
+ lock = URL.class;
+ }
+ return lock;
+ }
+
+ private static void resetURLStreamHandlers() throws IllegalAccessException {
+ Field handlersField = getField(URL.class, Hashtable.class, false);
+ if (handlersField != null) {
+ @SuppressWarnings("rawtypes")
+ Hashtable<?, ?> handlers = (Hashtable) handlersField.get(null);
+ if (handlers != null)
+ handlers.clear();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see java.lang.Runnable#run()
+ * This thread monitors the framework active status and terminates when the framework is
+ * shutdown. This is needed to ensure the VM does not exist because of the lack of a
+ * non-daemon thread (bug 215730)
+ */
+ public void run() {
+ synchronized (this) {
+ while (active)
+ try {
+ this.wait(1000);
+ } catch (InterruptedException e) {
+ // do nothing
+ }
+ }
+ }
+
+ void setForcedRestart(boolean forcedRestart) {
+ this.forcedRestart = forcedRestart;
+ }
+
+ boolean isForcedRestart() {
+ return forcedRestart;
+ }
+
+ public FrameworkEvent waitForStop(long timeout) throws InterruptedException {
+ boolean waitForEver = timeout == 0;
+ long start = System.currentTimeMillis();
+ long timeLeft = timeout;
+ synchronized (this) {
+ FrameworkEvent[] event = shutdownEvent;
+ while (event != null && event[0] == null) {
+ this.wait(timeLeft);
+ if (!waitForEver) {
+ timeLeft = start + timeout - System.currentTimeMillis();
+ // break if we are passed the timeout
+ if (timeLeft <= 0)
+ break;
+ }
+ }
+ if (event == null || event[0] == null)
+ return new FrameworkEvent(FrameworkEvent.WAIT_TIMEDOUT, systemBundle, null);
+ return event[0];
+ }
+ }
+
+ /**
+ * Used by ServiceReferenceImpl for isAssignableTo
+ * @param registrant Bundle registering service
+ * @param client Bundle desiring to use service
+ * @param className class name to use
+ * @param serviceClass class of original service object
+ * @return true if assignable given package wiring
+ */
+ public boolean isServiceAssignableTo(Bundle registrant, Bundle client, String className, Class<?> serviceClass) {
+ // always return false for fragments
+ AbstractBundle consumer = (AbstractBundle) client;
+ if (consumer.isFragment())
+ return false;
+ // 1) if the registrant == consumer always return true
+ AbstractBundle producer = (AbstractBundle) registrant;
+ if (consumer == producer)
+ return true;
+ // 2) get the package name from the specified className
+ String pkgName = BundleLoader.getPackageName(className);
+ if (pkgName.startsWith("java.")) //$NON-NLS-1$
+ return true;
+ BundleLoader producerBL = producer.getBundleLoader();
+ if (producerBL == null)
+ return false;
+ BundleLoader consumerBL = consumer.getBundleLoader();
+ if (consumerBL == null)
+ return false;
+ // 3) for the specified bundle, find the wiring for the package. If no wiring is found return true
+ PackageSource consumerSource = consumerBL.getPackageSource(pkgName);
+ if (consumerSource == null)
+ return true;
+ // work around the issue when the package is in the EE and we delegate to boot for that package
+ if (isBootDelegationPackage(pkgName)) {
+ SystemBundleLoader systemLoader = (SystemBundleLoader) systemBundle.getBundleLoader();
+ if (systemLoader.isEEPackage(pkgName))
+ return true; // in this case we have a common source from the EE
+ }
+ // 4) For the registrant bundle, find the wiring for the package.
+ PackageSource producerSource = producerBL.getPackageSource(pkgName);
+ if (producerSource == null) {
+ if (serviceClass != null && ServiceFactory.class.isAssignableFrom(serviceClass)) {
+ Bundle bundle = packageAdmin.getBundle(serviceClass);
+ if (bundle != null && bundle != registrant)
+ // in this case we have a wacky ServiceFactory that is doing something we cannot
+ // verify if it is correct. Instead of failing we allow the assignment and hope for the best
+ // bug 326918
+ return true;
+ }
+ // 5) If no wiring is found for the registrant bundle then find the wiring for the classloader of the service object. If no wiring is found return false.
+ producerSource = getPackageSource(serviceClass, pkgName);
+ if (producerSource == null)
+ return false;
+ }
+ // 6) If the two wirings found are equal then return true; otherwise return false.
+ return producerSource.hasCommonSource(consumerSource);
+ }
+
+ private PackageSource getPackageSource(Class<?> serviceClass, String pkgName) {
+ if (serviceClass == null)
+ return null;
+ AbstractBundle serviceBundle = (AbstractBundle) packageAdmin.getBundle(serviceClass);
+ if (serviceBundle == null)
+ return null;
+ BundleLoader producerBL = serviceBundle.getBundleLoader();
+ if (producerBL == null)
+ return null;
+ PackageSource producerSource = producerBL.getPackageSource(pkgName);
+ if (producerSource != null)
+ return producerSource;
+ // try the interfaces
+ Class<?>[] interfaces = serviceClass.getInterfaces();
+ // note that getInterfaces never returns null
+ for (int i = 0; i < interfaces.length; i++) {
+ producerSource = getPackageSource(interfaces[i], pkgName);
+ if (producerSource != null)
+ return producerSource;
+ }
+ // try super class
+ return getPackageSource(serviceClass.getSuperclass(), pkgName);
+ }
+
+ public boolean isBootDelegationPackage(String name) {
+ if (bootDelegateAll)
+ return true;
+ if (bootDelegation != null)
+ for (int i = 0; i < bootDelegation.length; i++)
+ if (name.equals(bootDelegation[i]))
+ return true;
+ if (bootDelegationStems != null)
+ for (int i = 0; i < bootDelegationStems.length; i++)
+ if (name.startsWith(bootDelegationStems[i]))
+ return true;
+ return false;
+ }
+
+ SignedContentFactory getSignedContentFactory() {
+ ServiceTracker<SignedContentFactory, SignedContentFactory> currentTracker = signedContentFactory;
+ return (currentTracker == null ? null : currentTracker.getService());
+ }
+
+ ContextFinder getContextFinder() {
+ return contextFinder;
+ }
+
+ public boolean isRefreshDuplicateBSNAllowed() {
+ return allowRefreshDuplicateBSN;
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/InternalSystemBundle.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/InternalSystemBundle.java
new file mode 100644
index 000000000..fb352f0de
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/InternalSystemBundle.java
@@ -0,0 +1,433 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.internal.core;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.security.Permission;
+import java.security.ProtectionDomain;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.osgi.framework.*;
+import org.osgi.framework.startlevel.FrameworkStartLevel;
+import org.osgi.framework.wiring.FrameworkWiring;
+
+/**
+ * This class subclasses Bundle to provide a system Bundle
+ * so that the framework can be represented as a bundle and
+ * can access the services provided by other bundles.
+ */
+
+public class InternalSystemBundle extends BundleHost implements org.osgi.framework.launch.Framework {
+ class SystemBundleHeaders extends Dictionary<String, String> {
+ private final Dictionary<String, String> headers;
+
+ public SystemBundleHeaders(Dictionary<String, String> headers) {
+ this.headers = headers;
+ }
+
+ public Enumeration<String> elements() {
+ return headers.elements();
+ }
+
+ public String get(Object key) {
+ if (!(key instanceof String))
+ return null;
+ if (org.osgi.framework.Constants.EXPORT_PACKAGE.equalsIgnoreCase((String) key)) {
+ return getExtra(org.osgi.framework.Constants.EXPORT_PACKAGE, org.osgi.framework.Constants.FRAMEWORK_SYSTEMPACKAGES, org.osgi.framework.Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA);
+ } else if (org.osgi.framework.Constants.PROVIDE_CAPABILITY.equalsIgnoreCase((String) key)) {
+ return getExtra(org.osgi.framework.Constants.PROVIDE_CAPABILITY, org.osgi.framework.Constants.FRAMEWORK_SYSTEMCAPABILITIES, org.osgi.framework.Constants.FRAMEWORK_SYSTEMCAPABILITIES_EXTRA);
+ }
+ return headers.get(key);
+ }
+
+ private String getExtra(String header, String systemProp, String systemExtraProp) {
+ String systemValue = FrameworkProperties.getProperty(systemProp);
+ String systemExtraValue = FrameworkProperties.getProperty(systemExtraProp);
+ if (systemValue == null)
+ systemValue = systemExtraValue;
+ else if (systemExtraValue != null && systemExtraValue.trim().length() > 0)
+ systemValue += ", " + systemExtraValue; //$NON-NLS-1$
+ String result = headers.get(header);
+ if (systemValue != null && systemValue.trim().length() > 0) {
+ if (result != null)
+ result += ", " + systemValue; //$NON-NLS-1$
+ else
+ result = systemValue;
+ }
+ return result;
+ }
+
+ public boolean isEmpty() {
+ return headers.isEmpty();
+ }
+
+ public Enumeration<String> keys() {
+ return headers.keys();
+ }
+
+ public String put(String key, String value) {
+ return headers.put(key, value);
+ }
+
+ public String remove(Object key) {
+ return headers.remove(key);
+ }
+
+ public int size() {
+ return headers.size();
+ }
+
+ }
+
+ private final FrameworkStartLevel fsl;
+ ProtectionDomain systemDomain;
+
+ /**
+ * Private SystemBundle object constructor.
+ * This method creates the SystemBundle and its BundleContext.
+ * The SystemBundle's state is set to STARTING.
+ * This method is called when the framework is constructed.
+ *
+ * @param framework Framework this bundle is running in
+ */
+ protected InternalSystemBundle(Framework framework) throws BundleException {
+ super(framework.adaptor.createSystemBundleData(), framework); // startlevel=0 means framework stopped
+ Constants.setInternalSymbolicName(bundledata.getSymbolicName());
+ state = Bundle.RESOLVED;
+ context = createContext();
+ fsl = new EquinoxStartLevel();
+ }
+
+ /**
+ * Load the bundle.
+ * This methods overrides the Bundle method and does nothing.
+ *
+ */
+ protected void load() {
+ SecurityManager sm = System.getSecurityManager();
+
+ if (sm != null) {
+ systemDomain = getClass().getProtectionDomain();
+ }
+ }
+
+ /**
+ * Reload from a new bundle.
+ * This methods overrides the Bundle method and does nothing.
+ *
+ * @param newBundle
+ * @return false
+ */
+ protected boolean reload(AbstractBundle newBundle) {
+ return (false);
+ }
+
+ /**
+ * Refresh the bundle.
+ * This methods overrides the Bundle method and does nothing.
+ *
+ */
+ protected void refresh() {
+ // do nothing
+ }
+
+ /**
+ * Unload the bundle.
+ * This methods overrides the Bundle method and does nothing.
+ *
+ * @return false
+ */
+ protected boolean unload() {
+ return (false);
+ }
+
+ /**
+ * Close the the Bundle's file.
+ * This method closes the BundleContext for the SystemBundle.
+ *
+ */
+ protected void close() {
+ context.close();
+ context = null;
+ }
+
+ /**
+ * This method loads a class from the bundle.
+ *
+ * @param name the name of the desired Class.
+ * @param checkPermission indicates whether a permission check should be done.
+ * @return the resulting Class
+ * @exception java.lang.ClassNotFoundException if the class definition was not found.
+ */
+ protected Class<?> loadClass(String name, boolean checkPermission) throws ClassNotFoundException {
+ if (checkPermission) {
+ framework.checkAdminPermission(this, AdminPermission.CLASS);
+ checkValid();
+ }
+ return (Class.forName(name));
+ }
+
+ /**
+ * Find the specified resource in this bundle.
+ * This methods returns null for the system bundle.
+ */
+ public URL getResource(String name) {
+ return (null);
+ }
+
+ /**
+ * Indicate SystemBundle is resolved.
+ *
+ */
+ protected boolean isUnresolved() {
+ return (false);
+ }
+
+ /**
+ * Start this bundle.
+ * This methods overrides the Bundle method and does nothing.
+ *
+ */
+ public void start() {
+ framework.checkAdminPermission(this, AdminPermission.EXECUTE);
+ }
+
+ public void start(int options) {
+ framework.checkAdminPermission(this, AdminPermission.EXECUTE);
+ }
+
+ /**
+ * Start the SystemBundle.
+ * This method launches the framework.
+ *
+ */
+ protected void resume() {
+ /* initialize the startlevel service */
+ framework.startLevelManager.initialize();
+
+ /* Load all installed bundles */
+ loadInstalledBundles(framework.startLevelManager.getInstalledBundles(framework.bundles, false));
+ /* Start the system bundle */
+ try {
+ framework.systemBundle.state = Bundle.STARTING;
+ framework.systemBundle.context.start();
+ framework.publishBundleEvent(BundleEvent.STARTING, framework.systemBundle);
+ } catch (BundleException be) {
+ if (Debug.DEBUG_STARTLEVEL) {
+ Debug.println("SLL: Bundle resume exception: " + be.getMessage()); //$NON-NLS-1$
+ Debug.printStackTrace(be.getNestedException() == null ? be : be.getNestedException());
+ }
+ framework.publishFrameworkEvent(FrameworkEvent.ERROR, framework.systemBundle, be);
+ throw new RuntimeException(be.getMessage(), be);
+ }
+
+ }
+
+ private void loadInstalledBundles(AbstractBundle[] installedBundles) {
+
+ for (int i = 0; i < installedBundles.length; i++) {
+ AbstractBundle bundle = installedBundles[i];
+ if (Debug.DEBUG_STARTLEVEL) {
+ Debug.println("SLL: Trying to load bundle " + bundle); //$NON-NLS-1$
+ }
+ bundle.load();
+ }
+ }
+
+ /**
+ * Stop the framework.
+ * This method spawns a thread which will call framework.shutdown.
+ *
+ */
+ public void stop() {
+ framework.checkAdminPermission(this, AdminPermission.EXECUTE);
+
+ if ((state & (ACTIVE | STARTING)) != 0) {
+ Thread shutdown = framework.secureAction.createThread(new Runnable() {
+ public void run() {
+ try {
+ framework.close();
+ } catch (Throwable t) {
+ // allow the adaptor to handle this unexpected error
+ framework.adaptor.handleRuntimeError(t);
+ }
+ }
+ }, "System Bundle Shutdown", framework.getContextFinder()); //$NON-NLS-1$
+
+ shutdown.start();
+ }
+ }
+
+ public void stop(int options) {
+ stop();
+ }
+
+ /**
+ * Stop the SystemBundle.
+ * This method shuts down the framework.
+ *
+ */
+ protected void suspend() {
+
+ framework.startLevelManager.shutdown();
+ framework.startLevelManager.cleanup();
+
+ /* clean up the exporting loaders */
+ framework.packageAdmin.cleanup();
+
+ if (Debug.DEBUG_GENERAL) {
+ Debug.println("->Framework shutdown"); //$NON-NLS-1$
+ }
+ // fire the STOPPED event here.
+ // All bundles have been unloaded, but there may be a boot strap listener that is interested (bug 182742)
+ framework.publishBundleEvent(BundleEvent.STOPPED, this);
+ }
+
+ protected void suspend(boolean lock) {
+ // do nothing
+ }
+
+ /**
+ * Update this bundle.
+ * This method spawns a thread which will call framework.shutdown
+ * followed by framework.launch.
+ *
+ */
+ public void update() {
+ framework.checkAdminPermission(this, AdminPermission.LIFECYCLE);
+
+ if ((state & (ACTIVE | STARTING)) != 0) {
+ Thread restart = framework.secureAction.createThread(new Runnable() {
+ public void run() {
+ int sl = framework.startLevelManager.getStartLevel();
+ FrameworkProperties.setProperty(Constants.PROP_OSGI_RELAUNCH, ""); //$NON-NLS-1$
+ framework.shutdown(FrameworkEvent.STOPPED_UPDATE);
+ framework.launch();
+ if (sl > 0)
+ framework.startLevelManager.doSetStartLevel(sl);
+ FrameworkProperties.clearProperty(Constants.PROP_OSGI_RELAUNCH);
+ }
+ }, "System Bundle Update", framework.getContextFinder()); //$NON-NLS-1$
+
+ restart.start();
+ }
+ }
+
+ /**
+ * Update this bundle from an InputStream.
+ * This methods overrides the Bundle method and does nothing.
+ *
+ * @param in The InputStream from which to read the new bundle.
+ */
+ public void update(InputStream in) {
+ update();
+
+ try {
+ in.close();
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+
+ /**
+ * Uninstall this bundle.
+ * This methods overrides the Bundle method and throws an exception.
+ *
+ */
+ public void uninstall() throws BundleException {
+ framework.checkAdminPermission(this, AdminPermission.LIFECYCLE);
+
+ throw new BundleException(Msg.BUNDLE_SYSTEMBUNDLE_UNINSTALL_EXCEPTION, BundleException.INVALID_OPERATION);
+ }
+
+ /**
+ * Determine whether the bundle has the requested
+ * permission.
+ * This methods overrides the Bundle method and returns <code>true</code>.
+ *
+ * @param permission The requested permission.
+ * @return <code>true</code>
+ */
+ public boolean hasPermission(Object permission) {
+ if (systemDomain != null) {
+ if (permission instanceof Permission) {
+ return systemDomain.implies((Permission) permission);
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * No work to do for the SystemBundle.
+ *
+ * @param refreshedBundles
+ * A list of bundles which have been refreshed as a result
+ * of a packageRefresh
+ */
+ protected void unresolvePermissions(AbstractBundle[] refreshedBundles) {
+ // Do nothing
+ }
+
+ public Dictionary<String, String> getHeaders(String localeString) {
+ return new SystemBundleHeaders(super.getHeaders(localeString));
+ }
+
+ public void init() {
+ // no op for internal representation
+ }
+
+ public FrameworkEvent waitForStop(long timeout) throws InterruptedException {
+ return framework.waitForStop(timeout);
+ }
+
+ public ClassLoader getClassLoader() {
+ return getClass().getClassLoader();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected <A> A adapt0(Class<A> adapterType) {
+ if (FrameworkStartLevel.class.equals(adapterType))
+ return (A) fsl;
+ else if (FrameworkWiring.class.equals(adapterType))
+ return (A) framework.getPackageAdmin();
+ return super.adapt0(adapterType);
+ }
+
+ class EquinoxStartLevel implements FrameworkStartLevel {
+ public void setStartLevel(int startlevel, FrameworkListener... listeners) {
+ framework.startLevelManager.setStartLevel(startlevel, InternalSystemBundle.this, listeners);
+ }
+
+ public int getInitialBundleStartLevel() {
+ return framework.startLevelManager.getInitialBundleStartLevel();
+ }
+
+ public void setInitialBundleStartLevel(int startlevel) {
+ framework.startLevelManager.setInitialBundleStartLevel(startlevel);
+ }
+
+ public Bundle getBundle() {
+ return InternalSystemBundle.this;
+ }
+
+ public int getStartLevel() {
+ return framework.startLevelManager.getStartLevel();
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/ManifestLocalization.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/ManifestLocalization.java
new file mode 100644
index 000000000..5212b1733
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/ManifestLocalization.java
@@ -0,0 +1,241 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.framework.internal.core;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.*;
+import org.eclipse.osgi.framework.util.Headers;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+
+/**
+ * This class is used by the Bundle Class to localize manifest headers.
+ */
+public class ManifestLocalization {
+ final static String DEFAULT_ROOT = FrameworkProperties.getProperty("equinox.root.locale", "en"); //$NON-NLS-1$ //$NON-NLS-2$
+ private final AbstractBundle bundle;
+ private final Dictionary<String, String> rawHeaders;
+ private Dictionary<String, String> defaultLocaleHeaders = null;
+ private final Hashtable<String, BundleResourceBundle> cache = new Hashtable<String, BundleResourceBundle>(5);
+
+ public ManifestLocalization(AbstractBundle bundle, Dictionary<String, String> rawHeaders) {
+ this.bundle = bundle;
+ this.rawHeaders = rawHeaders;
+ }
+
+ Dictionary<String, String> getHeaders(String localeString) {
+ if (localeString.length() == 0)
+ return rawHeaders;
+ boolean isDefaultLocale = localeString.equals(Locale.getDefault().toString());
+ Dictionary<String, String> currentDefault = defaultLocaleHeaders;
+ if (isDefaultLocale && currentDefault != null) {
+ return currentDefault;
+ }
+ try {
+ bundle.checkValid();
+ } catch (IllegalStateException ex) {
+ // defaultLocaleHeaders should have been initialized on uninstall
+ if (currentDefault != null)
+ return currentDefault;
+ return rawHeaders;
+ }
+ ResourceBundle localeProperties = getResourceBundle(localeString, isDefaultLocale);
+ Enumeration<String> e = this.rawHeaders.keys();
+ Headers<String, String> localeHeaders = new Headers<String, String>(this.rawHeaders.size());
+ while (e.hasMoreElements()) {
+ String key = e.nextElement();
+ String value = this.rawHeaders.get(key);
+ if (value.startsWith("%") && (value.length() > 1)) { //$NON-NLS-1$
+ String propertiesKey = value.substring(1);
+ try {
+ value = localeProperties == null ? propertiesKey : (String) localeProperties.getObject(propertiesKey);
+ } catch (MissingResourceException ex) {
+ value = propertiesKey;
+ }
+ }
+ localeHeaders.set(key, value);
+ }
+ localeHeaders.setReadOnly();
+ if (isDefaultLocale) {
+ defaultLocaleHeaders = localeHeaders;
+ }
+ return (localeHeaders);
+ }
+
+ private String[] buildNLVariants(String nl) {
+ List<String> result = new ArrayList<String>();
+ while (nl.length() > 0) {
+ result.add(nl);
+ int i = nl.lastIndexOf('_');
+ nl = (i < 0) ? "" : nl.substring(0, i); //$NON-NLS-1$
+ }
+ result.add(""); //$NON-NLS-1$
+ return result.toArray(new String[result.size()]);
+ }
+
+ /*
+ * This method find the appropriate Manifest Localization file inside the
+ * bundle. If not found, return null.
+ */
+ ResourceBundle getResourceBundle(String localeString, boolean isDefaultLocale) {
+ BundleResourceBundle resourceBundle = lookupResourceBundle(localeString);
+ if (isDefaultLocale)
+ return (ResourceBundle) resourceBundle;
+ // need to determine if this is resource bundle is an empty stem
+ // if it is then the default locale should be used
+ if (resourceBundle == null || resourceBundle.isStemEmpty())
+ return (ResourceBundle) lookupResourceBundle(Locale.getDefault().toString());
+ return (ResourceBundle) resourceBundle;
+ }
+
+ private BundleResourceBundle lookupResourceBundle(String localeString) {
+ // get the localization header as late as possible to avoid accessing the raw headers
+ // getting the first value from the raw headers forces the manifest to be parsed (bug 332039)
+ String localizationHeader = rawHeaders.get(Constants.BUNDLE_LOCALIZATION);
+ if (localizationHeader == null)
+ localizationHeader = Constants.BUNDLE_LOCALIZATION_DEFAULT_BASENAME;
+ synchronized (cache) {
+ BundleResourceBundle result = cache.get(localeString);
+ if (result != null)
+ return result.isEmpty() ? null : result;
+ String[] nlVarients = buildNLVariants(localeString);
+ BundleResourceBundle parent = null;
+ for (int i = nlVarients.length - 1; i >= 0; i--) {
+ BundleResourceBundle varientBundle = null;
+ URL varientURL = findResource(localizationHeader + (nlVarients[i].equals("") ? nlVarients[i] : '_' + nlVarients[i]) + ".properties"); //$NON-NLS-1$ //$NON-NLS-2$
+ if (varientURL == null) {
+ varientBundle = cache.get(nlVarients[i]);
+ } else {
+ InputStream resourceStream = null;
+ try {
+ resourceStream = varientURL.openStream();
+ varientBundle = new LocalizationResourceBundle(resourceStream);
+ } catch (IOException e) {
+ // ignore and continue
+ } finally {
+ if (resourceStream != null) {
+ try {
+ resourceStream.close();
+ } catch (IOException e3) {
+ //Ignore exception
+ }
+ }
+ }
+ }
+
+ if (varientBundle == null) {
+ varientBundle = new EmptyResouceBundle(nlVarients[i]);
+ }
+ if (parent != null)
+ varientBundle.setParent((ResourceBundle) parent);
+ cache.put(nlVarients[i], varientBundle);
+ parent = varientBundle;
+ }
+ result = cache.get(localeString);
+ return result.isEmpty() ? null : result;
+ }
+ }
+
+ private URL findResource(String resource) {
+ AbstractBundle searchBundle = bundle;
+ if (bundle.isResolved()) {
+ if (bundle.isFragment() && bundle.getHosts() != null) {
+ //if the bundle is a fragment, look in the host first
+ searchBundle = bundle.getHosts()[0];
+ if (searchBundle.getState() == Bundle.UNINSTALLED)
+ searchBundle = bundle;
+ }
+ return findInResolved(resource, searchBundle);
+ }
+ return searchBundle.getEntry0(resource);
+ }
+
+ private static URL findInResolved(String filePath, AbstractBundle bundleHost) {
+ URL result = bundleHost.getEntry0(filePath);
+ if (result != null)
+ return result;
+ return findInFragments(filePath, bundleHost);
+ }
+
+ private static URL findInFragments(String filePath, AbstractBundle searchBundle) {
+ BundleFragment[] fragments = searchBundle.getFragments();
+ URL fileURL = null;
+ for (int i = 0; fragments != null && i < fragments.length && fileURL == null; i++) {
+ if (fragments[i].getState() != Bundle.UNINSTALLED)
+ fileURL = fragments[i].getEntry0(filePath);
+ }
+ return fileURL;
+ }
+
+ private interface BundleResourceBundle {
+ void setParent(ResourceBundle parent);
+
+ boolean isEmpty();
+
+ boolean isStemEmpty();
+ }
+
+ private class LocalizationResourceBundle extends PropertyResourceBundle implements BundleResourceBundle {
+ public LocalizationResourceBundle(InputStream in) throws IOException {
+ super(in);
+ }
+
+ public void setParent(ResourceBundle parent) {
+ super.setParent(parent);
+ }
+
+ public boolean isEmpty() {
+ return false;
+ }
+
+ public boolean isStemEmpty() {
+ return parent == null;
+ }
+ }
+
+ class EmptyResouceBundle extends ResourceBundle implements BundleResourceBundle {
+ private final String localeString;
+
+ public EmptyResouceBundle(String locale) {
+ super();
+ this.localeString = locale;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Enumeration<String> getKeys() {
+ return Collections.enumeration(Collections.EMPTY_LIST);
+ }
+
+ protected Object handleGetObject(String arg0) throws MissingResourceException {
+ return null;
+ }
+
+ public void setParent(ResourceBundle parent) {
+ super.setParent(parent);
+ }
+
+ public boolean isEmpty() {
+ if (parent == null)
+ return true;
+ return ((BundleResourceBundle) parent).isEmpty();
+ }
+
+ public boolean isStemEmpty() {
+ if (DEFAULT_ROOT.equals(localeString))
+ return false;
+ if (parent == null)
+ return true;
+ return ((BundleResourceBundle) parent).isStemEmpty();
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/PackageAdminImpl.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/PackageAdminImpl.java
new file mode 100644
index 000000000..f9e14605d
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/PackageAdminImpl.java
@@ -0,0 +1,766 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.internal.core;
+
+import java.io.IOException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.*;
+import org.eclipse.osgi.framework.adaptor.*;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.internal.loader.BundleLoader;
+import org.eclipse.osgi.internal.loader.BundleLoaderProxy;
+import org.eclipse.osgi.internal.profile.Profile;
+import org.eclipse.osgi.service.resolver.*;
+import org.eclipse.osgi.service.resolver.VersionRange;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.*;
+import org.osgi.framework.wiring.FrameworkWiring;
+import org.osgi.service.packageadmin.*;
+
+/**
+ * PackageAdmin service for the OSGi specification.
+ *
+ * Framework service which allows bundle programmers to inspect the packages
+ * exported in the framework and eagerly update or uninstall bundles.
+ *
+ * If present, there will only be a single instance of this service
+ * registered in the framework.
+ *
+ * <p> The term <i>exported package</i> (and the corresponding interface
+ * {@link ExportedPackage}) refers to a package that has actually been
+ * exported (as opposed to one that is available for export).
+ *
+ * <p> Note that the information about exported packages returned by this
+ * service is valid only until the next time {@link #refreshPackages(org.osgi.framework.Bundle[])} is
+ * called.
+ * If an ExportedPackage becomes stale, (that is, the package it references
+ * has been updated or removed as a result of calling
+ * PackageAdmin.refreshPackages()),
+ * its getName() and getSpecificationVersion() continue to return their
+ * old values, isRemovalPending() returns true, and getExportingBundle()
+ * and getImportingBundles() return null.
+ */
+public class PackageAdminImpl implements PackageAdmin, FrameworkWiring {
+ /** framework object */
+ protected Framework framework;
+
+ /*
+ * We need to make sure that the GetBundleAction class loads early to prevent a ClassCircularityError when checking permissions.
+ * See bug 161561
+ */
+ static {
+ Class<?> c;
+ c = GetBundleAction.class;
+ c.getName(); // to prevent compiler warnings
+ }
+
+ static class GetBundleAction implements PrivilegedAction<Bundle> {
+ private Class<?> clazz;
+ private PackageAdminImpl impl;
+
+ public GetBundleAction(PackageAdminImpl impl, Class<?> clazz) {
+ this.impl = impl;
+ this.clazz = clazz;
+ }
+
+ public Bundle run() {
+ return impl.getBundlePriv(clazz);
+ }
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param framework Framework object.
+ */
+ protected PackageAdminImpl(Framework framework) {
+ this.framework = framework;
+ }
+
+ public ExportedPackage[] getExportedPackages(Bundle bundle) {
+ List<ExportedPackage> allExports = new ArrayList<ExportedPackage>();
+ FrameworkAdaptor adaptor = framework.adaptor;
+ if (adaptor == null)
+ return null;
+ ExportPackageDescription[] allDescriptions = adaptor.getState().getExportedPackages();
+ for (int i = 0; i < allDescriptions.length; i++) {
+ ExportedPackageImpl exportedPackage = createExportedPackage(allDescriptions[i]);
+ if (exportedPackage == null)
+ continue;
+ if (bundle == null || exportedPackage.getBundle() == bundle)
+ allExports.add(exportedPackage);
+ }
+ return (allExports.size() == 0 ? null : allExports.toArray(new ExportedPackage[allExports.size()]));
+ }
+
+ private ExportedPackageImpl createExportedPackage(ExportPackageDescription description) {
+ BundleDescription exporter = description.getExporter();
+ if (exporter == null || exporter.getHost() != null)
+ return null;
+ Object userObject = exporter.getUserObject();
+ if (!(userObject instanceof BundleLoaderProxy)) {
+ BundleHost bundle = (BundleHost) framework.getBundle(exporter.getBundleId());
+ if (bundle == null)
+ return null;
+ userObject = bundle.getLoaderProxy();
+ }
+ return new ExportedPackageImpl(description, (BundleLoaderProxy) userObject);
+ }
+
+ public ExportedPackage getExportedPackage(String name) {
+ ExportedPackage[] allExports = getExportedPackages((Bundle) null);
+ if (allExports == null)
+ return null;
+ ExportedPackage result = null;
+ for (int i = 0; i < allExports.length; i++) {
+ if (name.equals(allExports[i].getName())) {
+ if (result == null) {
+ result = allExports[i];
+ } else {
+ Version curVersion = result.getVersion();
+ Version newVersion = allExports[i].getVersion();
+ if (newVersion.compareTo(curVersion) >= 0)
+ result = allExports[i];
+ }
+ }
+ }
+ return result;
+ }
+
+ public ExportedPackage[] getExportedPackages(String name) {
+ ExportedPackage[] allExports = getExportedPackages((Bundle) null);
+ if (allExports == null)
+ return null;
+ List<ExportedPackage> result = new ArrayList<ExportedPackage>(1); // rare to have more than one
+ for (int i = 0; i < allExports.length; i++)
+ if (name.equals(allExports[i].getName()))
+ result.add(allExports[i]);
+ return (result.size() == 0 ? null : result.toArray(new ExportedPackage[result.size()]));
+ }
+
+ public void refreshPackages(Bundle[] input) {
+ refreshPackages(input, false, null);
+ }
+
+ public void refreshPackages(Bundle[] input, boolean synchronously, final FrameworkListener[] listeners) {
+ framework.checkAdminPermission(framework.systemBundle, AdminPermission.RESOLVE);
+
+ final AbstractBundle[] copy;
+ if (input != null) {
+ synchronized (input) {
+ copy = new AbstractBundle[input.length];
+ System.arraycopy(input, 0, copy, 0, input.length);
+ }
+ } else
+ copy = null;
+
+ if (synchronously) {
+ doResolveBundles(copy, true, listeners);
+ if (framework.isForcedRestart())
+ framework.systemBundle.stop();
+ } else {
+ Thread refresh = framework.secureAction.createThread(new Runnable() {
+ public void run() {
+ doResolveBundles(copy, true, listeners);
+ if (framework.isForcedRestart())
+ framework.shutdown(FrameworkEvent.STOPPED_BOOTCLASSPATH_MODIFIED);
+ }
+ }, "Refresh Packages", framework.getContextFinder()); //$NON-NLS-1$
+ refresh.start();
+ }
+ }
+
+ public boolean resolveBundles(Bundle[] bundles) {
+ return resolveBundles(bundles, false);
+ }
+
+ boolean resolveBundles(Bundle[] bundles, boolean propagateError) {
+ framework.checkAdminPermission(framework.systemBundle, AdminPermission.RESOLVE);
+ if (bundles == null)
+ bundles = framework.getAllBundles();
+ try {
+ doResolveBundles(bundles, false, null);
+ } catch (ResolverHookException e) {
+ if (propagateError)
+ throw e;
+ }
+ for (int i = 0; i < bundles.length; i++)
+ if (!((AbstractBundle) bundles[i]).isResolved())
+ return false;
+
+ return true;
+ }
+
+ // This method is protected to enable a work around to bug 245251
+ synchronized protected void doResolveBundles(Bundle[] bundles, boolean refreshPackages, FrameworkListener[] listeners) {
+ try {
+ if (Profile.PROFILE && Profile.STARTUP)
+ Profile.logEnter("resolve bundles"); //$NON-NLS-1$
+ framework.publishBundleEvent(Framework.BATCHEVENT_BEGIN, framework.systemBundle);
+ State systemState = framework.adaptor.getState();
+ BundleDescription[] descriptions = null;
+ int numBundles = bundles == null ? 0 : bundles.length;
+ if (!refreshPackages) {
+ List<BundleDescription> resolving = new ArrayList<BundleDescription>();
+ for (Bundle bundle : bundles) {
+ BundleDescription description = ((AbstractBundle) bundle).getBundleDescription();
+ if (((bundle.getState() & Bundle.INSTALLED) != 0) && description != null)
+ resolving.add(description);
+ }
+ descriptions = resolving.toArray(new BundleDescription[resolving.size()]);
+ } else if (numBundles > 0) {
+ // populate the resolved hosts package sources first (do this outside sync block: bug 280929)
+ populateLoaders(framework.getAllBundles());
+ synchronized (framework.bundles) {
+ // now collect the descriptions to refresh
+ List<BundleDescription> results = new ArrayList<BundleDescription>(numBundles);
+ for (int i = 0; i < numBundles; i++) {
+ BundleDescription description = ((AbstractBundle) bundles[i]).getBundleDescription();
+ if (description != null && description.getBundleId() != 0 && !results.contains(description))
+ results.add(description);
+ if (framework.isRefreshDuplicateBSNAllowed()) {
+ // add in any bundles that have the same symbolic name see bug (169593)
+ AbstractBundle[] sameNames = framework.bundles.getBundles(bundles[i].getSymbolicName());
+ if (sameNames != null && sameNames.length > 1) {
+ for (int j = 0; j < sameNames.length; j++)
+ if (sameNames[j] != bundles[i]) {
+ BundleDescription sameName = sameNames[j].getBundleDescription();
+ if (sameName != null && sameName.getBundleId() != 0 && !results.contains(sameName)) {
+ if (checkExtensionBundle(sameName))
+ results.add(sameName);
+ }
+ }
+ }
+ }
+ }
+ descriptions = (results.size() == 0 ? null : results.toArray(new BundleDescription[results.size()]));
+ }
+ }
+ StateDelta stateDelta = systemState.resolve(descriptions, refreshPackages);
+ BundleDelta[] delta = stateDelta.getChanges();
+ processDelta(delta, refreshPackages, systemState);
+ if (stateDelta.getResovlerHookException() != null)
+ throw stateDelta.getResovlerHookException();
+ } catch (Throwable t) {
+ if (Debug.DEBUG_PACKAGEADMIN) {
+ Debug.println("PackageAdminImpl.doResolveBundles: Error occured :"); //$NON-NLS-1$
+ Debug.printStackTrace(t);
+ }
+ if (t instanceof RuntimeException)
+ throw (RuntimeException) t;
+ if (t instanceof Error)
+ throw (Error) t;
+ } finally {
+ if (Profile.PROFILE && Profile.STARTUP)
+ Profile.logExit("resolve bundles"); //$NON-NLS-1$
+ if (framework.isActive()) {
+ framework.publishBundleEvent(Framework.BATCHEVENT_END, framework.systemBundle);
+ if (refreshPackages)
+ framework.publishFrameworkEvent(FrameworkEvent.PACKAGES_REFRESHED, framework.systemBundle, null, listeners);
+ }
+ }
+ }
+
+ private void populateLoaders(AbstractBundle[] bundles) {
+ // populate all the loaders with their package source information
+ // this is needed to fix bug 259903.
+ for (int i = 0; i < bundles.length; i++) {
+ // only need to do this for host bundles which are resolved
+ if (bundles[i] instanceof BundleHost && bundles[i].isResolved()) {
+ // getting the BundleLoader object populates the require-bundle sources
+ BundleLoader loader = ((BundleHost) bundles[i]).getBundleLoader();
+ if (loader != null)
+ // need to explicitly get the import package sources
+ loader.getImportedSources(null);
+ }
+ }
+ }
+
+ private boolean checkExtensionBundle(BundleDescription sameName) {
+ if (sameName.getHost() == null || !sameName.isResolved())
+ return true; // only do this extra check for resolved fragment bundles
+ // only add fragments if they are not for the system bundle
+ if (((BundleDescription) sameName.getHost().getSupplier()).getBundleId() != 0)
+ return true;
+ // never do this for resolved system bundle fragments
+ return false;
+ }
+
+ private void resumeBundles(AbstractBundle[] bundles, boolean refreshPackages, int[] previousStates) {
+ if (Debug.DEBUG_PACKAGEADMIN) {
+ Debug.println("PackageAdminImpl: restart the bundles"); //$NON-NLS-1$
+ }
+ if (bundles == null)
+ return;
+ for (int i = 0; i < bundles.length; i++) {
+ if (!bundles[i].isResolved() || (!refreshPackages && ((bundles[i].getBundleData().getStatus() & Constants.BUNDLE_LAZY_START) == 0 || bundles[i].testStateChanging(Thread.currentThread()))))
+ // skip bundles that are not resolved or
+ // if we are doing resolveBundles then skip non-lazy start bundles and bundles currently changing state by this thread
+ continue;
+ if (previousStates[i] == Bundle.ACTIVE)
+ try {
+ bundles[i].start(Bundle.START_TRANSIENT);
+ } catch (BundleException e) {
+ framework.publishFrameworkEvent(FrameworkEvent.ERROR, bundles[i], e);
+ }
+ else
+ framework.resumeBundle(bundles[i]);
+ }
+ }
+
+ private void suspendBundle(AbstractBundle bundle) {
+ // attempt to suspend the bundle or obtain the state change lock
+ // Note that this may fail but we cannot quit the
+ // refreshPackages operation because of it. (bug 84169)
+ if (bundle.isActive() && !bundle.isFragment()) {
+ framework.suspendBundle(bundle, true);
+ } else {
+ if (bundle.getStateChanging() != Thread.currentThread())
+ try {
+ bundle.beginStateChange();
+ } catch (BundleException e) {
+ framework.publishFrameworkEvent(FrameworkEvent.ERROR, bundle, e);
+ }
+ }
+
+ if (Debug.DEBUG_PACKAGEADMIN) {
+ if (bundle.stateChanging == null) {
+ Debug.println("Bundle state change lock is clear! " + bundle); //$NON-NLS-1$
+ Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$
+ }
+ }
+ }
+
+ private void applyRemovalPending(BundleDelta bundleDelta) throws BundleException {
+ if ((bundleDelta.getType() & BundleDelta.REMOVAL_COMPLETE) != 0) {
+ BundleDescription bundle = bundleDelta.getBundle();
+ if (bundle.getDependents() != null && bundle.getDependents().length > 0) {
+ /* Reaching here is an internal error */
+ if (Debug.DEBUG_PACKAGEADMIN) {
+ Debug.println("Bundles still depend on removed bundle! " + bundle); //$NON-NLS-1$
+ Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$
+ }
+ throw new BundleException(Msg.OSGI_INTERNAL_ERROR);
+ }
+ Object userObject = bundle.getUserObject();
+ if (userObject instanceof BundleLoaderProxy) {
+ BundleLoader.closeBundleLoader((BundleLoaderProxy) userObject);
+ try {
+ ((BundleLoaderProxy) userObject).getBundleData().close();
+ } catch (IOException e) {
+ // ignore
+ }
+ } else if (userObject instanceof BundleData) {
+ try {
+ ((BundleData) userObject).close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+ }
+
+ private AbstractBundle setResolved(BundleDescription bundleDescription) {
+ if (!bundleDescription.isResolved())
+ return null;
+ AbstractBundle bundle = framework.getBundle(bundleDescription.getBundleId());
+ if (bundle == null) {
+ BundleException be = new BundleException(NLS.bind(Msg.BUNDLE_NOT_IN_FRAMEWORK, bundleDescription));
+ framework.publishFrameworkEvent(FrameworkEvent.ERROR, framework.systemBundle, be);
+ return null;
+ }
+ boolean resolve = true;
+ if (bundle.isFragment()) {
+ BundleDescription[] hosts = bundleDescription.getHost().getHosts();
+ for (int i = 0; i < hosts.length; i++) {
+ BundleHost host = (BundleHost) framework.getBundle(hosts[i].getBundleId());
+ resolve = ((BundleFragment) bundle).addHost(host);
+ }
+ }
+ if (resolve)
+ bundle.resolve();
+ return bundle;
+ }
+
+ private void applyDeltas(BundleDelta[] bundleDeltas) throws BundleException {
+ Arrays.sort(bundleDeltas, new Comparator<BundleDelta>() {
+ public int compare(BundleDelta delta0, BundleDelta delta1) {
+ return (int) (delta0.getBundle().getBundleId() - delta1.getBundle().getBundleId());
+ }
+ });
+ for (int i = 0; i < bundleDeltas.length; i++) {
+ int type = bundleDeltas[i].getType();
+ if ((type & (BundleDelta.REMOVAL_PENDING | BundleDelta.REMOVAL_COMPLETE)) != 0)
+ applyRemovalPending(bundleDeltas[i]);
+ if ((type & BundleDelta.RESOLVED) != 0) {
+ AbstractBundle bundle = setResolved(bundleDeltas[i].getBundle());
+ if (bundle != null && bundle.isResolved()) {
+ NativeCodeSpecification nativeCode = bundleDeltas[i].getBundle().getNativeCodeSpecification();
+ if (nativeCode != null && nativeCode.getSupplier() != null)
+ try {
+ BundleData data = bundle.getBundleData();
+ data.installNativeCode(((NativeCodeDescription) nativeCode.getSupplier()).getNativePaths());
+ } catch (BundleException e) {
+ framework.publishFrameworkEvent(FrameworkEvent.ERROR, bundle, e);
+ }
+ }
+ }
+ }
+ }
+
+ private AbstractBundle[] processDelta(BundleDelta[] bundleDeltas, boolean refreshPackages, State systemState) {
+ List<AbstractBundle> bundlesList = new ArrayList<AbstractBundle>(bundleDeltas.length);
+ // get all the bundles that are going to be refreshed
+ for (int i = 0; i < bundleDeltas.length; i++) {
+ if ((bundleDeltas[i].getType() & BundleDelta.REMOVAL_COMPLETE) != 0 && (bundleDeltas[i].getType() & BundleDelta.REMOVED) == 0)
+ // this means the bundle was previously pending removal; do not add to list because it was already removed from before.
+ continue;
+ AbstractBundle changedBundle = framework.getBundle(bundleDeltas[i].getBundle().getBundleId());
+ if (changedBundle != null && !bundlesList.contains(changedBundle))
+ bundlesList.add(changedBundle);
+ }
+ AbstractBundle[] refresh = bundlesList.toArray(new AbstractBundle[bundlesList.size()]);
+ // first sort by id/start-level order
+ Util.sort(refresh, 0, refresh.length);
+ // then sort by dependency order
+ framework.startLevelManager.sortByDependency(refresh);
+ boolean[] previouslyResolved = new boolean[refresh.length];
+ int[] previousStates = new int[refresh.length];
+ try {
+ try {
+ if (Debug.DEBUG_PACKAGEADMIN) {
+ Debug.println("refreshPackages: Suspend each bundle and acquire its state change lock"); //$NON-NLS-1$
+ }
+ // find which bundles were previously resolved and handle extension bundles
+ boolean restart = false;
+ for (int i = refresh.length - 1; i >= 0; i--) {
+ previouslyResolved[i] = refresh[i].isResolved();
+ if (refresh[i] == framework.systemBundle)
+ restart = true;
+ else if (((refresh[i].bundledata.getType() & BundleData.TYPE_FRAMEWORK_EXTENSION) != 0) && previouslyResolved[i])
+ restart = true;
+ else if ((refresh[i].bundledata.getType() & BundleData.TYPE_BOOTCLASSPATH_EXTENSION) != 0)
+ restart = true;
+ else if ((refresh[i].bundledata.getType() & BundleData.TYPE_EXTCLASSPATH_EXTENSION) != 0 && previouslyResolved[i])
+ restart = true;
+ }
+ if (restart) {
+ FrameworkProperties.setProperty("osgi.forcedRestart", "true"); //$NON-NLS-1$ //$NON-NLS-2$
+ framework.setForcedRestart(true);
+ // do not shutdown the framework while holding the PackageAdmin lock (bug 194149)
+ return null;
+ }
+ // now suspend each bundle and grab its state change lock.
+ if (refreshPackages)
+ for (int i = refresh.length - 1; i >= 0; i--) {
+ previousStates[i] = refresh[i].getState();
+ suspendBundle(refresh[i]);
+ }
+ /*
+ * Refresh the bundles which will unexport the packages.
+ * This will move RESOLVED bundles to the INSTALLED state.
+ */
+ if (Debug.DEBUG_PACKAGEADMIN) {
+ Debug.println("refreshPackages: refresh the bundles"); //$NON-NLS-1$
+ }
+
+ synchronized (framework.bundles) {
+ for (int i = refresh.length - 1; i >= 0; i--)
+ refresh[i].refresh();
+ }
+ // send out unresolved events outside synch block (defect #80610)
+ // send out unresolved events in reverse dependency order (defect #207505)
+ for (int i = refresh.length - 1; i >= 0; i--) {
+ // send out unresolved events
+ if (previouslyResolved[i])
+ framework.publishBundleEvent(BundleEvent.UNRESOLVED, refresh[i]);
+ }
+
+ /*
+ * apply Deltas.
+ */
+ if (Debug.DEBUG_PACKAGEADMIN) {
+ Debug.println("refreshPackages: applying deltas to bundles"); //$NON-NLS-1$
+ }
+ synchronized (framework.bundles) {
+ applyDeltas(bundleDeltas);
+ }
+
+ } finally {
+ /*
+ * Release the state change locks.
+ */
+ if (Debug.DEBUG_PACKAGEADMIN) {
+ Debug.println("refreshPackages: release the state change locks"); //$NON-NLS-1$
+ }
+ if (refreshPackages)
+ for (int i = 0; i < refresh.length; i++) {
+ AbstractBundle changedBundle = refresh[i];
+ changedBundle.completeStateChange();
+ }
+ }
+ /*
+ * Take this opportunity to clean up the adaptor storage.
+ */
+ if (refreshPackages) {
+ if (Debug.DEBUG_PACKAGEADMIN)
+ Debug.println("refreshPackages: clean up adaptor storage"); //$NON-NLS-1$
+ try {
+ framework.adaptor.compactStorage();
+ } catch (IOException e) {
+ if (Debug.DEBUG_PACKAGEADMIN) {
+ Debug.println("refreshPackages exception: " + e.getMessage()); //$NON-NLS-1$
+ Debug.printStackTrace(e);
+ }
+ framework.publishFrameworkEvent(FrameworkEvent.ERROR, framework.systemBundle, new BundleException(Msg.BUNDLE_REFRESH_FAILURE, e));
+ }
+ }
+ } catch (BundleException e) {
+ if (Debug.DEBUG_PACKAGEADMIN) {
+ Debug.println("refreshPackages exception: " + e.getMessage()); //$NON-NLS-1$
+ Debug.printStackTrace(e.getNestedException() == null ? e : e.getNestedException());
+ }
+ framework.publishFrameworkEvent(FrameworkEvent.ERROR, framework.systemBundle, new BundleException(Msg.BUNDLE_REFRESH_FAILURE, e));
+ }
+
+ // send out any resolved. This must be done after the state change locks have been release.
+ if (Debug.DEBUG_PACKAGEADMIN)
+ Debug.println("refreshPackages: send out RESOLVED events"); //$NON-NLS-1$
+ for (int i = 0; i < refresh.length; i++)
+ if (refresh[i].isResolved())
+ framework.publishBundleEvent(BundleEvent.RESOLVED, refresh[i]);
+
+ // if we end up refreshing the system bundle or one of its fragments the framework will be shutdown and
+ // should be re-started. This call should return without doing further work.
+ if (!framework.isActive())
+ return refresh;
+ if (refreshPackages) {
+ // must clear permission class and condition cache
+ framework.securityAdmin.clearCaches();
+ // increment the system state timestamp if we are refreshing packages.
+ // this is needed incase we suspended a bundle from processing the delta (bug 167483)
+ if (bundleDeltas.length > 0)
+ systemState.setTimeStamp(systemState.getTimeStamp() == Long.MAX_VALUE ? 0 : systemState.getTimeStamp() + 1);
+ }
+ // always resume bundles incase we have lazy-start bundles
+ resumeBundles(refresh, refreshPackages, previousStates);
+ return refresh;
+ }
+
+ public RequiredBundle[] getRequiredBundles(String symbolicName) {
+ AbstractBundle[] bundles;
+ if (symbolicName == null)
+ bundles = framework.getAllBundles();
+ else
+ bundles = framework.getBundleBySymbolicName(symbolicName);
+ if (bundles == null || bundles.length == 0)
+ return null;
+
+ List<RequiredBundle> result = new ArrayList<RequiredBundle>(bundles.length);
+ for (int i = 0; i < bundles.length; i++) {
+ if (bundles[i].isFragment() || !bundles[i].isResolved() || bundles[i].getSymbolicName() == null)
+ continue;
+ if (bundles[i].hasPermission(new BundlePermission(bundles[i].getSymbolicName(), BundlePermission.PROVIDE)))
+ result.add(((BundleHost) bundles[i]).getLoaderProxy());
+ }
+ return result.size() == 0 ? null : result.toArray(new RequiredBundle[result.size()]);
+ }
+
+ public Bundle[] getBundles(String symbolicName, String versionRange) {
+ if (symbolicName == null) {
+ throw new IllegalArgumentException();
+ }
+ AbstractBundle bundles[] = framework.getBundleBySymbolicName(symbolicName);
+ if (bundles == null)
+ return null;
+
+ if (versionRange == null) {
+ AbstractBundle[] result = new AbstractBundle[bundles.length];
+ System.arraycopy(bundles, 0, result, 0, result.length);
+ return result;
+ }
+
+ // This code depends on the array of bundles being in descending
+ // version order.
+ List<AbstractBundle> result = new ArrayList<AbstractBundle>(bundles.length);
+ VersionRange range = new VersionRange(versionRange);
+ for (int i = 0; i < bundles.length; i++) {
+ if (range.isIncluded(bundles[i].getVersion())) {
+ result.add(bundles[i]);
+ }
+ }
+
+ if (result.size() == 0)
+ return null;
+ return result.toArray(new AbstractBundle[result.size()]);
+ }
+
+ public Bundle[] getFragments(Bundle bundle) {
+ return ((AbstractBundle) bundle).getFragments();
+ }
+
+ public Bundle[] getHosts(Bundle bundle) {
+ BundleHost[] hosts = ((AbstractBundle) bundle).getHosts();
+ if (hosts == null)
+ return null;
+ // copy the array to protect modification
+ Bundle[] result = new Bundle[hosts.length];
+ for (int i = 0; i < hosts.length; i++)
+ result[i] = hosts[i];
+ return result;
+ }
+
+ Bundle getBundlePriv(Class<?> clazz) {
+ ClassLoader cl = clazz.getClassLoader();
+ if (cl instanceof BundleClassLoader) {
+ ClassLoaderDelegate delegate = ((BundleClassLoader) cl).getDelegate();
+ if (delegate instanceof BundleLoader)
+ return ((BundleLoader) delegate).getBundle();
+ }
+ if (cl == getClass().getClassLoader())
+ return framework.systemBundle;
+ return null;
+ }
+
+ public Bundle getBundle(@SuppressWarnings("rawtypes") final Class clazz) {
+ if (System.getSecurityManager() == null)
+ return getBundlePriv(clazz);
+ return AccessController.doPrivileged(new GetBundleAction(this, clazz));
+ }
+
+ public int getBundleType(Bundle bundle) {
+ return ((AbstractBundle) bundle).isFragment() ? PackageAdmin.BUNDLE_TYPE_FRAGMENT : 0;
+ }
+
+ protected void cleanup() {
+ //This is only called when the framework is shutting down
+ }
+
+ protected void setResolvedBundles(InternalSystemBundle systemBundle) {
+ checkSystemBundle(systemBundle);
+ // Now set the actual state of the bundles from the persisted state.
+ State state = framework.adaptor.getState();
+ BundleDescription[] descriptions = state.getBundles();
+ for (int i = 0; i < descriptions.length; i++) {
+ if (descriptions[i].getBundleId() == 0)
+ setFrameworkVersion(descriptions[i]);
+ else
+ setResolved(descriptions[i]);
+ }
+ }
+
+ private void checkSystemBundle(InternalSystemBundle systemBundle) {
+ try {
+ // first check that the system bundle has not changed since last saved state.
+ State state = framework.adaptor.getState();
+ BundleDescription oldSystemBundle = state.getBundle(0);
+ boolean different = false;
+ if (oldSystemBundle == null || !systemBundle.getBundleData().getVersion().equals(oldSystemBundle.getVersion()))
+ different = true;
+ if (!different && FrameworkProperties.getProperty("osgi.dev") == null) //$NON-NLS-1$
+ return; // return quick if not in dev mode; system bundle version changes with each build
+ BundleDescription newSystemBundle = state.getFactory().createBundleDescription(state, systemBundle.getHeaders(""), systemBundle.getLocation(), 0); //$NON-NLS-1$
+ if (newSystemBundle == null)
+ throw new BundleException(Msg.OSGI_SYSTEMBUNDLE_DESCRIPTION_ERROR);
+ if (!different) {
+ // need to check to make sure the system bundle description is up to date in the state.
+ ExportPackageDescription[] oldPackages = oldSystemBundle.getExportPackages();
+ ExportPackageDescription[] newPackages = newSystemBundle.getExportPackages();
+ if (oldPackages.length >= newPackages.length) {
+ for (int i = 0; i < newPackages.length && !different; i++) {
+ if (oldPackages[i].getName().equals(newPackages[i].getName())) {
+ Object oldVersion = oldPackages[i].getVersion();
+ Object newVersion = newPackages[i].getVersion();
+ different = oldVersion == null ? newVersion != null : !oldVersion.equals(newVersion);
+ } else {
+ different = true;
+ }
+ }
+ } else {
+ different = true;
+ }
+ }
+ if (different) {
+ state.removeBundle(0);
+ state.addBundle(newSystemBundle);
+ // force resolution so packages are properly linked
+ state.resolve(false);
+ }
+ } catch (BundleException e) /* fatal error */{
+ e.printStackTrace();
+ throw new RuntimeException(NLS.bind(Msg.OSGI_SYSTEMBUNDLE_CREATE_EXCEPTION, e.getMessage()), e);
+ }
+ }
+
+ private void setFrameworkVersion(BundleDescription systemBundle) {
+ ExportPackageDescription[] packages = systemBundle.getExportPackages();
+ for (int i = 0; i < packages.length; i++)
+ if (packages[i].getName().equals(Constants.OSGI_FRAMEWORK_PACKAGE)) {
+ FrameworkProperties.setProperty(Constants.FRAMEWORK_VERSION, packages[i].getVersion().toString());
+ break;
+ }
+ FrameworkProperties.setProperty(Constants.OSGI_IMPL_VERSION_KEY, systemBundle.getVersion().toString());
+ }
+
+ public Bundle getBundle() {
+ return framework.getBundle(0);
+ }
+
+ public void refreshBundles(Collection<Bundle> bundles, FrameworkListener... listeners) {
+ refreshPackages(bundles == null ? null : bundles.toArray(new Bundle[bundles.size()]), false, listeners);
+ }
+
+ public boolean resolveBundles(Collection<Bundle> bundles) {
+ return resolveBundles(bundles == null ? null : bundles.toArray(new Bundle[bundles.size()]));
+ }
+
+ public Collection<Bundle> getRemovalPendingBundles() {
+ // TODO need to consolidate our removal pending tracking.
+ // We currently have three places this is kept (PackageAdminImpl, StateImpl and ResolverImpl)
+ // Using the state's because it has easy access to the uninstalled Bundle objects
+ BundleDescription[] removals = framework.adaptor.getState().getRemovalPending();
+ Set<Bundle> result = new HashSet<Bundle>();
+ for (int i = 0; i < removals.length; i++) {
+ Object ref = removals[i].getUserObject();
+ if (ref instanceof BundleReference)
+ result.add(((BundleReference) ref).getBundle());
+ }
+ return result;
+ }
+
+ public Collection<Bundle> getDependencyClosure(Collection<Bundle> bundles) {
+ Collection<BundleDescription> descriptions = getDescriptionClosure(bundles);
+ Set<Bundle> result = new HashSet<Bundle>();
+ for (BundleDescription description : descriptions) {
+ Object userObject = description.getUserObject();
+ if (userObject instanceof BundleReference) {
+ Bundle bundle = ((BundleReference) userObject).getBundle();
+ if (bundle != null)
+ result.add(bundle);
+ }
+ }
+ return result;
+ }
+
+ private Collection<BundleDescription> getDescriptionClosure(Collection<Bundle> bundles) {
+ State state = framework.adaptor.getState();
+ Collection<BundleDescription> descriptions = new ArrayList<BundleDescription>();
+ for (Bundle bundle : bundles) {
+ BundleDescription description = state.getBundle(bundle.getBundleId());
+ if (description != null)
+ descriptions.add(description);
+ }
+ return state.getDependencyClosure(descriptions);
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/ReferenceInputStream.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/ReferenceInputStream.java
new file mode 100644
index 000000000..9a1064856
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/ReferenceInputStream.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.internal.core;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+/**
+ * InputStream subclass which provides a reference (via URL) to the data
+ * rather than allowing the input stream to be directly read.
+ */
+public class ReferenceInputStream extends InputStream {
+ protected URL reference;
+
+ public ReferenceInputStream(URL reference) {
+ this.reference = reference;
+ }
+
+ /* This method should not be called.
+ */
+ public int read() throws IOException {
+ throw new IOException();
+ }
+
+ public URL getReference() {
+ return reference;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/StartLevelEvent.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/StartLevelEvent.java
new file mode 100644
index 000000000..10636bc64
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/StartLevelEvent.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.internal.core;
+
+import java.util.EventObject;
+import org.osgi.framework.FrameworkListener;
+
+/**
+ * StartLevel Event for the OSGi framework.
+ *
+ * Event which signifies that a start level change has been requested for the framework or for a bundle.
+ *
+ */
+class StartLevelEvent extends EventObject {
+ private static final long serialVersionUID = 3258125839085155891L;
+ public final static int CHANGE_BUNDLE_SL = 0x00000000;
+ public final static int CHANGE_FW_SL = 0x00000001;
+
+ /**
+ * Event Type
+ */
+ private final transient int type;
+
+ /**
+ * StartLevel - value depends on event type:
+ * CHANGE_BUNDLE_SL - value is the new bundle startlevel
+ * CHANGE_FW_SL - value is the new framework startlevel
+ *
+ */
+ private final transient int newSl;
+
+ /**
+ * For a change in bundle startlevel, this is the bundle to be changed.
+ * For a change in framework startlevel, this is the bundle requesting the change.
+ */
+ private final transient AbstractBundle bundle;
+
+ /**
+ * A list of framework listeners that must be called at the end of the operation.
+ */
+ private final transient FrameworkListener[] listeners;
+
+ /**
+ * Creates a StartLevel event regarding the specified bundle.
+ *
+ * @param type The type of startlevel event (inc or dec)
+ * @param newSl the ultimate requested startlevel we are on our way to
+ * @param bundle The affected bundle, or system bundle if it is for the framework
+ */
+ public StartLevelEvent(int type, int newSl, AbstractBundle bundle, FrameworkListener... listeners) {
+ super(bundle);
+ this.type = type;
+ this.newSl = newSl;
+ this.bundle = bundle;
+ this.listeners = listeners;
+ }
+
+ public int getType() {
+ return this.type;
+ }
+
+ public int getNewSL() {
+ return this.newSl;
+ }
+
+ public AbstractBundle getBundle() {
+ return this.bundle;
+ }
+
+ public FrameworkListener[] getListeners() {
+ return listeners;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/StartLevelManager.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/StartLevelManager.java
new file mode 100644
index 000000000..efd697c99
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/StartLevelManager.java
@@ -0,0 +1,678 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.internal.core;
+
+import java.io.IOException;
+import java.security.*;
+import java.util.*;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.framework.eventmgr.*;
+import org.eclipse.osgi.service.resolver.BundleDescription;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.*;
+import org.osgi.service.startlevel.StartLevel;
+
+/**
+ * StartLevel service implementation for the OSGi specification.
+ *
+ * Framework service which allows management of framework and bundle startlevels.
+ *
+ * This class also acts as the StartLevel service factory class, providing StartLevel objects
+ * to those requesting org.osgi.service.startlevel.StartLevel service.
+ *
+ * If present, there will only be a single instance of this service
+ * registered in the framework.
+ */
+public class StartLevelManager implements EventDispatcher<Object, Object, StartLevelEvent>, StartLevel {
+ protected static EventManager eventManager;
+ protected static Map<Object, Object> startLevelListeners;
+
+ /** The initial bundle start level for newly installed bundles */
+ protected int initialBundleStartLevel = 1;
+ // default value is 1 for compatibility mode
+
+ /** The currently active framework start level */
+ private int activeSL = 0;
+
+ /** An object used to lock the active startlevel while it is being referenced */
+ private final Object lock = new Object();
+ private final Framework framework;
+
+ /** This constructor is called by the Framework */
+ protected StartLevelManager(Framework framework) {
+ this.framework = framework;
+ }
+
+ protected void initialize() {
+ initialBundleStartLevel = framework.adaptor.getInitialBundleStartLevel();
+
+ // create an event manager and a start level listener
+ // note that we do not pass the ContextFinder because it is set each time doSetStartLevel is called
+ eventManager = new EventManager("Start Level Event Dispatcher"); //$NON-NLS-1$
+ startLevelListeners = new CopyOnWriteIdentityMap<Object, Object>();
+ startLevelListeners.put(this, this);
+ }
+
+ protected void cleanup() {
+ eventManager.close();
+ eventManager = null;
+ startLevelListeners.clear();
+ startLevelListeners = null;
+ }
+
+ /**
+ * Return the initial start level value that is assigned
+ * to a Bundle when it is first installed.
+ *
+ * @return The initial start level value for Bundles.
+ * @see #setInitialBundleStartLevel
+ */
+ public int getInitialBundleStartLevel() {
+ return initialBundleStartLevel;
+ }
+
+ /**
+ * Set the initial start level value that is assigned
+ * to a Bundle when it is first installed.
+ *
+ * <p>The initial bundle start level will be set to the specified start level. The
+ * initial bundle start level value will be persistently recorded
+ * by the Framework.
+ *
+ * <p>When a Bundle is installed via <tt>BundleContext.installBundle</tt>,
+ * it is assigned the initial bundle start level value.
+ *
+ * <p>The default initial bundle start level value is 1
+ * unless this method has been
+ * called to assign a different initial bundle
+ * start level value.
+ *
+ * <p>This method does not change the start level values of installed
+ * bundles.
+ *
+ * @param startlevel The initial start level for newly installed bundles.
+ * @throws IllegalArgumentException If the specified start level is less than or
+ * equal to zero.
+ * @throws SecurityException if the caller does not have the
+ * <tt>AdminPermission</tt> and the Java runtime environment supports
+ * permissions.
+ */
+ public void setInitialBundleStartLevel(int startlevel) {
+ framework.checkAdminPermission(framework.systemBundle, AdminPermission.STARTLEVEL);
+ if (startlevel <= 0) {
+ throw new IllegalArgumentException();
+ }
+ initialBundleStartLevel = startlevel;
+ framework.adaptor.setInitialBundleStartLevel(startlevel);
+ }
+
+ /**
+ * Return the active start level value of the Framework.
+ *
+ * If the Framework is in the process of changing the start level
+ * this method must return the active start level if this
+ * differs from the requested start level.
+ *
+ * @return The active start level value of the Framework.
+ */
+ public int getStartLevel() {
+ return activeSL;
+ }
+
+ /**
+ * Modify the active start level of the Framework.
+ *
+ * <p>The Framework will move to the requested start level. This method
+ * will return immediately to the caller and the start level
+ * change will occur asynchronously on another thread.
+ *
+ * <p>If the specified start level is
+ * higher than the active start level, the
+ * Framework will continue to increase the start level
+ * until the Framework has reached the specified start level,
+ * starting bundles at each
+ * start level which are persistently marked to be started as described in the
+ * <tt>Bundle.start</tt> method.
+ *
+ * At each intermediate start level value on the
+ * way to and including the target start level, the framework must:
+ * <ol>
+ * <li>Change the active start level to the intermediate start level value.
+ * <li>Start bundles at the intermediate start level in
+ * ascending order by <tt>Bundle.getBundleId</tt>.
+ * </ol>
+ * When this process completes after the specified start level is reached,
+ * the Framework will broadcast a Framework event of
+ * type <tt>FrameworkEvent.STARTLEVEL_CHANGED</tt> to announce it has moved to the specified
+ * start level.
+ *
+ * <p>If the specified start level is lower than the active start level, the
+ * Framework will continue to decrease the start level
+ * until the Framework has reached the specified start level
+ * stopping bundles at each
+ * start level as described in the <tt>Bundle.stop</tt> method except that their
+ * persistently recorded state indicates that they must be restarted in the
+ * future.
+ *
+ * At each intermediate start level value on the
+ * way to and including the specified start level, the framework must:
+ * <ol>
+ * <li>Stop bundles at the intermediate start level in
+ * descending order by <tt>Bundle.getBundleId</tt>.
+ * <li>Change the active start level to the intermediate start level value.
+ * </ol>
+ * When this process completes after the specified start level is reached,
+ * the Framework will broadcast a Framework event of
+ * type <tt>FrameworkEvent.STARTLEVEL_CHANGED</tt> to announce it has moved to the specified
+ * start level.
+ *
+ * <p>If the specified start level is equal to the active start level, then
+ * no bundles are started or stopped, however, the Framework must broadcast
+ * a Framework event of type <tt>FrameworkEvent.STARTLEVEL_CHANGED</tt> to
+ * announce it has finished moving to the specified start level. This
+ * event may arrive before the this method return.
+ *
+ * @param newSL The requested start level for the Framework.
+ * @throws IllegalArgumentException If the specified start level is less than or
+ * equal to zero.
+ * @throws SecurityException If the caller does not have the
+ * <tt>AdminPermission</tt> and the Java runtime environment supports
+ * permissions.
+ */
+ public void setStartLevel(int newSL, org.osgi.framework.Bundle callerBundle, FrameworkListener... listeners) {
+ if (newSL <= 0) {
+ throw new IllegalArgumentException(NLS.bind(Msg.STARTLEVEL_EXCEPTION_INVALID_REQUESTED_STARTLEVEL, "" + newSL)); //$NON-NLS-1$
+ }
+ framework.checkAdminPermission(framework.systemBundle, AdminPermission.STARTLEVEL);
+
+ if (Debug.DEBUG_STARTLEVEL) {
+ Debug.println("StartLevelImpl: setStartLevel: " + newSL + "; callerBundle = " + callerBundle.getBundleId()); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ issueEvent(new StartLevelEvent(StartLevelEvent.CHANGE_FW_SL, newSL, (AbstractBundle) callerBundle, listeners));
+
+ }
+
+ public void setStartLevel(int newSL) {
+ setStartLevel(newSL, framework.systemBundle);
+ }
+
+ /**
+ * Internal method to shut down the framework synchronously by setting the startlevel to zero
+ * and calling the StartLevelListener worker calls directly
+ *
+ * This method does not return until all bundles are stopped and the framework is shut down.
+ */
+ protected void shutdown() {
+ doSetStartLevel(0);
+ }
+
+ /**
+ * Internal worker method to set the startlevel
+ *
+ * @param newSL start level value
+ * @param callerBundle - the bundle initiating the change in start level
+ */
+ void doSetStartLevel(int newSL, FrameworkListener... listeners) {
+ synchronized (lock) {
+ ClassLoader previousTCCL = Thread.currentThread().getContextClassLoader();
+ ClassLoader contextFinder = framework.getContextFinder();
+ if (contextFinder == previousTCCL)
+ contextFinder = null;
+ else
+ Thread.currentThread().setContextClassLoader(contextFinder);
+ try {
+ int tempSL = activeSL;
+ if (newSL > tempSL) {
+ boolean launching = tempSL == 0;
+ for (int i = tempSL; i < newSL; i++) {
+ if (Debug.DEBUG_STARTLEVEL) {
+ Debug.println("sync - incrementing Startlevel from " + tempSL); //$NON-NLS-1$
+ }
+ tempSL++;
+ // Note that we must get a new list of installed bundles each time;
+ // this is because additional bundles could have been installed from the previous start-level
+ incFWSL(i + 1, getInstalledBundles(framework.bundles, false));
+ }
+ if (launching) {
+ framework.systemBundle.state = Bundle.ACTIVE;
+ framework.publishBundleEvent(BundleEvent.STARTED, framework.systemBundle);
+ framework.publishFrameworkEvent(FrameworkEvent.STARTED, framework.systemBundle, null);
+ }
+ } else {
+ AbstractBundle[] sortedBundles = getInstalledBundles(framework.bundles, true);
+ for (int i = tempSL; i > newSL; i--) {
+ if (Debug.DEBUG_STARTLEVEL) {
+ Debug.println("sync - decrementing Startlevel from " + tempSL); //$NON-NLS-1$
+ }
+ tempSL--;
+ decFWSL(i - 1, sortedBundles);
+ }
+ if (newSL == 0) {
+ // unload all bundles
+ unloadAllBundles(framework.bundles);
+ stopSystemBundle();
+ }
+ }
+ framework.publishFrameworkEvent(FrameworkEvent.STARTLEVEL_CHANGED, framework.systemBundle, null, listeners);
+ if (Debug.DEBUG_STARTLEVEL) {
+ Debug.println("StartLevelImpl: doSetStartLevel: STARTLEVEL_CHANGED event published"); //$NON-NLS-1$
+ }
+ } catch (Error e) {
+ framework.publishFrameworkEvent(FrameworkEvent.ERROR, framework.systemBundle, e, listeners);
+ throw e;
+ } catch (RuntimeException e) {
+ framework.publishFrameworkEvent(FrameworkEvent.ERROR, framework.systemBundle, e, listeners);
+ throw e;
+ } finally {
+ if (contextFinder != null)
+ Thread.currentThread().setContextClassLoader(previousTCCL);
+ }
+ }
+ }
+
+ /**
+ * This method is used within the package to save the actual active startlevel value for the framework.
+ * Externally the setStartLevel method must be used.
+ *
+ * @param newSL - the new startlevel to save
+ */
+ protected void saveActiveStartLevel(int newSL) {
+ synchronized (lock) {
+ activeSL = newSL;
+ }
+ }
+
+ /**
+ * Return the persistent state of the specified bundle.
+ *
+ * <p>This method returns the persistent state of a bundle.
+ * The persistent state of a bundle indicates whether a bundle
+ * is persistently marked to be started when it's start level is
+ * reached.
+ *
+ * @return <tt>true</tt> if the bundle is persistently marked to be started,
+ * <tt>false</tt> if the bundle is not persistently marked to be started.
+ * @exception java.lang.IllegalArgumentException If the specified bundle has been uninstalled.
+ */
+ public boolean isBundlePersistentlyStarted(org.osgi.framework.Bundle bundle) {
+ return ((AbstractBundle) bundle).isPersistentlyStarted();
+ }
+
+ public boolean isBundleActivationPolicyUsed(Bundle bundle) {
+ return ((AbstractBundle) bundle).isActivationPolicyUsed();
+ }
+
+ /**
+ * Return the assigned start level value for the specified Bundle.
+ *
+ * @param bundle The target bundle.
+ * @return The start level value of the specified Bundle.
+ * @exception java.lang.IllegalArgumentException If the specified bundle has been uninstalled.
+ */
+ public int getBundleStartLevel(org.osgi.framework.Bundle bundle) {
+ return ((AbstractBundle) bundle).getStartLevel();
+ }
+
+ /**
+ * Assign a start level value to the specified Bundle.
+ *
+ * <p>The specified bundle will be assigned the specified start level. The
+ * start level value assigned to the bundle will be persistently recorded
+ * by the Framework.
+ *
+ * If the new start level for the bundle is lower than or equal to the active start level of
+ * the Framework, the Framework will start the specified bundle as described
+ * in the <tt>Bundle.start</tt> method if the bundle is persistently marked
+ * to be started. The actual starting of this bundle must occur asynchronously.
+ *
+ * If the new start level for the bundle is higher than the active start level of
+ * the Framework, the Framework will stop the specified bundle as described
+ * in the <tt>Bundle.stop</tt> method except that the persistently recorded
+ * state for the bundle indicates that the bundle must be restarted in the
+ * future. The actual stopping of this bundle must occur asynchronously.
+ *
+ * @param bundle The target bundle.
+ * @param newSL The new start level for the specified Bundle.
+ * @throws IllegalArgumentException
+ * If the specified bundle has been uninstalled or
+ * if the specified start level is less than or equal to zero, or the specified bundle is
+ * the system bundle.
+ * @throws SecurityException if the caller does not have the
+ * <tt>AdminPermission</tt> and the Java runtime environment supports
+ * permissions.
+ */
+ public void setBundleStartLevel(org.osgi.framework.Bundle bundle, int newSL) {
+
+ String exceptionText = null;
+ if (bundle.getBundleId() == 0) { // system bundle has id=0
+ exceptionText = Msg.STARTLEVEL_CANT_CHANGE_SYSTEMBUNDLE_STARTLEVEL;
+ } else if (bundle.getState() == Bundle.UNINSTALLED) {
+ exceptionText = NLS.bind(Msg.BUNDLE_UNINSTALLED_EXCEPTION, ((AbstractBundle) bundle).getBundleData().getLocation());
+ } else if (newSL <= 0) {
+ exceptionText = NLS.bind(Msg.STARTLEVEL_EXCEPTION_INVALID_REQUESTED_STARTLEVEL, "" + newSL); //$NON-NLS-1$
+ }
+ if (exceptionText != null)
+ throw new IllegalArgumentException(exceptionText);
+ // first check the permission of the caller
+ framework.checkAdminPermission(bundle, AdminPermission.EXECUTE);
+ try {
+ // if the bundle's startlevel is not already at the requested startlevel
+ if (newSL != ((org.eclipse.osgi.framework.internal.core.AbstractBundle) bundle).getInternalStartLevel()) {
+ final AbstractBundle b = (AbstractBundle) bundle;
+ b.getBundleData().setStartLevel(newSL);
+ try {
+ AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
+ public Object run() throws Exception {
+ b.getBundleData().save();
+ return null;
+ }
+ });
+ } catch (PrivilegedActionException e) {
+ if (e.getException() instanceof IOException) {
+ throw (IOException) e.getException();
+ }
+ throw (RuntimeException) e.getException();
+ }
+ // handle starting or stopping the bundle asynchronously
+ issueEvent(new StartLevelEvent(StartLevelEvent.CHANGE_BUNDLE_SL, newSL, (AbstractBundle) bundle));
+ }
+ } catch (IOException e) {
+ framework.publishFrameworkEvent(FrameworkEvent.ERROR, bundle, e);
+ }
+
+ }
+
+ /**
+ * This method sends the StartLevelEvent to the EventManager for dispatching
+ *
+ * @param sle The event to be queued to the Event Manager
+ */
+ private void issueEvent(StartLevelEvent sle) {
+
+ /* queue to hold set of listeners */
+ ListenerQueue<Object, Object, StartLevelEvent> queue = new ListenerQueue<Object, Object, StartLevelEvent>(eventManager);
+
+ /* add set of StartLevelListeners to queue */
+ queue.queueListeners(startLevelListeners.entrySet(), this);
+
+ /* dispatch event to set of listeners */
+ queue.dispatchEventAsynchronous(sle.getType(), sle);
+ }
+
+ /**
+ * This method is the call back that is called once for each listener.
+ * This method must cast the EventListener object to the appropriate listener
+ * class for the event type and call the appropriate listener method.
+ *
+ * @param listener This listener must be cast to the appropriate listener
+ * class for the events created by this source and the appropriate listener method
+ * must then be called.
+ * @param listenerObject This is the optional object that was passed to
+ * EventListeners.addListener when the listener was added to the EventListeners.
+ * @param eventAction This value was passed to the ListenerQueue object via one of its
+ * dispatchEvent* method calls. It can provide information (such
+ * as which listener method to call) so that this method
+ * can complete the delivery of the event to the listener.
+ * @param event This object was passed to the ListenerQueue object via one of its
+ * dispatchEvent* method calls. This object was created by the event source and
+ * is passed to this method. It should contain all the necessary information (such
+ * as what event object to pass) so that this method
+ * can complete the delivery of the event to the listener.
+ */
+ public void dispatchEvent(Object listener, Object listenerObject, int eventAction, StartLevelEvent event) {
+ try {
+ switch (eventAction) {
+ case StartLevelEvent.CHANGE_BUNDLE_SL :
+ setBundleSL(event);
+ break;
+ case StartLevelEvent.CHANGE_FW_SL :
+ doSetStartLevel(event.getNewSL(), event.getListeners());
+ break;
+ }
+ } catch (Throwable t) {
+ // allow the adaptor to handle this unexpected error
+ framework.adaptor.handleRuntimeError(t);
+ }
+ }
+
+ /**
+ * Increment the active startlevel by one
+ */
+ protected void incFWSL(int incToSL, AbstractBundle[] launchBundles) {
+ if (Debug.DEBUG_STARTLEVEL) {
+ Debug.println("SLL: incFWSL: saving activeSL of " + incToSL); //$NON-NLS-1$
+ }
+ // save the startlevel
+ saveActiveStartLevel(incToSL);
+ // resume all bundles at the startlevel
+ resumeBundles(launchBundles, incToSL);
+ }
+
+ /**
+ * Build an array of all installed bundles to be launch.
+ * The returned array is sorted by increasing startlevel/id order.
+ * @param bundles - the bundles installed in the framework
+ * @return A sorted array of bundles
+ */
+ AbstractBundle[] getInstalledBundles(BundleRepository bundles, boolean sortByDependency) {
+
+ /* make copy of bundles vector in case it is modified during launch */
+ AbstractBundle[] installedBundles;
+
+ synchronized (bundles) {
+ List<AbstractBundle> allBundles = bundles.getBundles();
+ installedBundles = new AbstractBundle[allBundles.size()];
+ allBundles.toArray(installedBundles);
+
+ /* sort bundle array in ascending startlevel / bundle id order
+ * so that bundles are started in ascending order.
+ */
+ Util.sort(installedBundles, 0, installedBundles.length);
+ if (sortByDependency)
+ sortByDependency(installedBundles);
+ }
+ return installedBundles;
+ }
+
+ void sortByDependency(AbstractBundle[] bundles) {
+ synchronized (framework.bundles) {
+ if (bundles.length <= 1)
+ return;
+ int currentSL = bundles[0].getInternalStartLevel();
+ int currentSLindex = 0;
+ boolean lazy = false;
+ for (int i = 0; i < bundles.length; i++) {
+ if (currentSL != bundles[i].getInternalStartLevel()) {
+ if (lazy)
+ sortByDependencies(bundles, currentSLindex, i);
+ currentSL = bundles[i].getInternalStartLevel();
+ currentSLindex = i;
+ lazy = false;
+ }
+ lazy |= (bundles[i].getBundleData().getStatus() & Constants.BUNDLE_LAZY_START) != 0;
+ }
+ // sort the last set of bundles
+ if (lazy)
+ sortByDependencies(bundles, currentSLindex, bundles.length);
+ }
+ }
+
+ private void sortByDependencies(AbstractBundle[] bundles, int start, int end) {
+ if (end - start <= 1)
+ return;
+ List<BundleDescription> descList = new ArrayList<BundleDescription>(end - start);
+ List<AbstractBundle> missingDescs = new ArrayList<AbstractBundle>(0);
+ for (int i = start; i < end; i++) {
+ BundleDescription desc = bundles[i].getBundleDescription();
+ if (desc != null)
+ descList.add(desc);
+ else
+ missingDescs.add(bundles[i]);
+ }
+ if (descList.size() <= 1)
+ return;
+ BundleDescription[] descriptions = descList.toArray(new BundleDescription[descList.size()]);
+ framework.adaptor.getPlatformAdmin().getStateHelper().sortBundles(descriptions);
+ for (int i = start; i < descriptions.length + start; i++)
+ bundles[i] = framework.bundles.getBundle(descriptions[i - start].getBundleId());
+ if (missingDescs.size() > 0) {
+ Iterator<AbstractBundle> missing = missingDescs.iterator();
+ for (int i = start + descriptions.length; i < end && missing.hasNext(); i++)
+ bundles[i] = missing.next();
+ }
+ }
+
+ /**
+ * Resume all bundles in the launch list at the specified start-level
+ * @param launch a list of Bundle Objects to launch
+ * @param currentSL the current start-level that the bundles must meet to be resumed
+ */
+ private void resumeBundles(AbstractBundle[] launch, int currentSL) {
+ // Resume all bundles that were previously started and whose startlevel is <= the active startlevel
+ // first resume the lazy activated bundles
+ resumeBundles(launch, true, currentSL);
+ // now resume all non lazy bundles
+ resumeBundles(launch, false, currentSL);
+ }
+
+ private void resumeBundles(AbstractBundle[] launch, boolean lazyOnly, int currentSL) {
+ for (int i = 0; i < launch.length && !framework.isForcedRestart(); i++) {
+ int bsl = launch[i].getInternalStartLevel();
+ if (bsl < currentSL) {
+ // skip bundles who should have already been started
+ continue;
+ } else if (bsl == currentSL) {
+ if (Debug.DEBUG_STARTLEVEL) {
+ Debug.println("SLL: Active sl = " + currentSL + "; Bundle " + launch[i].getBundleId() + " sl = " + bsl); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ boolean isLazyStart = launch[i].isLazyStart();
+ if (lazyOnly ? isLazyStart : !isLazyStart)
+ framework.resumeBundle(launch[i]);
+ } else {
+ // can stop resuming bundles since any remaining bundles have a greater startlevel than the framework active startlevel
+ break;
+ }
+ }
+ }
+
+ /**
+ * Decrement the active startlevel by one
+ * @param decToSL - the startlevel value to set the framework to
+ */
+ protected void decFWSL(int decToSL, AbstractBundle[] shutdown) {
+ if (Debug.DEBUG_STARTLEVEL) {
+ Debug.println("SLL: decFWSL: saving activeSL of " + decToSL); //$NON-NLS-1$
+ }
+
+ saveActiveStartLevel(decToSL);
+
+ // just decrementing the active startlevel - framework is not shutting down
+ // Do not check framework.isForcedRestart here because we want to stop the active bundles regardless.
+ for (int i = shutdown.length - 1; i >= 0; i--) {
+ int bsl = shutdown[i].getInternalStartLevel();
+ if (bsl > decToSL + 1)
+ // skip bundles who should have already been stopped
+ continue;
+ else if (bsl <= decToSL)
+ // stopped all bundles we are going to for this start level
+ break;
+ else if (shutdown[i].isActive()) {
+ // if bundle is active or starting, then stop the bundle
+ if (Debug.DEBUG_STARTLEVEL)
+ Debug.println("SLL: stopping bundle " + shutdown[i].getBundleId()); //$NON-NLS-1$
+ framework.suspendBundle(shutdown[i], false);
+ }
+ }
+ }
+
+ /**
+ * Stops the system bundle
+ */
+ private void stopSystemBundle() {
+ try {
+ framework.systemBundle.context.stop();
+ } catch (BundleException sbe) {
+ if (Debug.DEBUG_STARTLEVEL) {
+ Debug.println("SLL: Bundle suspend exception: " + sbe.getMessage()); //$NON-NLS-1$
+ Debug.printStackTrace(sbe.getNestedException() == null ? sbe : sbe.getNestedException());
+ }
+
+ framework.publishFrameworkEvent(FrameworkEvent.ERROR, framework.systemBundle, sbe);
+ }
+
+ framework.systemBundle.state = Bundle.RESOLVED;
+ framework.publishBundleEvent(BundleEvent.STOPPED, framework.systemBundle);
+ }
+
+ /**
+ * Unloads all bundles in the vector passed in.
+ * @param bundles list of Bundle objects to be unloaded
+ */
+ private void unloadAllBundles(BundleRepository bundles) {
+ synchronized (bundles) {
+ /* unload all installed bundles */
+ List<AbstractBundle> allBundles = bundles.getBundles();
+ int size = allBundles.size();
+
+ for (int i = 0; i < size; i++) {
+ AbstractBundle bundle = allBundles.get(i);
+
+ if (Debug.DEBUG_STARTLEVEL) {
+ Debug.println("SLL: Trying to unload bundle " + bundle); //$NON-NLS-1$
+ }
+ bundle.refresh();
+ try {
+ // make sure we close all the bundle data objects
+ bundle.getBundleData().close();
+ } catch (IOException e) {
+ // ignore, we are shutting down anyway
+ }
+ }
+ }
+ }
+
+ /**
+ * Set the bundle's startlevel to the new value
+ * This may cause the bundle to start or stop based on the active framework startlevel
+ * @param startLevelEvent - the event requesting change in bundle startlevel
+ */
+ protected void setBundleSL(StartLevelEvent startLevelEvent) {
+ synchronized (lock) {
+ int currentSL = getStartLevel();
+ int newSL = startLevelEvent.getNewSL();
+ AbstractBundle bundle = startLevelEvent.getBundle();
+
+ if (Debug.DEBUG_STARTLEVEL) {
+ Debug.print("SLL: bundle active=" + bundle.isActive()); //$NON-NLS-1$
+ Debug.print("; newSL = " + newSL); //$NON-NLS-1$
+ Debug.println("; activeSL = " + currentSL); //$NON-NLS-1$
+ }
+
+ if (bundle.isActive() && (newSL > currentSL)) {
+ if (Debug.DEBUG_STARTLEVEL) {
+ Debug.println("SLL: stopping bundle " + bundle.getBundleId()); //$NON-NLS-1$
+ }
+ framework.suspendBundle(bundle, false);
+ } else {
+ if (!bundle.isActive() && (newSL <= currentSL)) {
+ if (Debug.DEBUG_STARTLEVEL) {
+ Debug.println("SLL: starting bundle " + bundle.getBundleId()); //$NON-NLS-1$
+ }
+ framework.resumeBundle(bundle);
+ }
+ }
+ if (Debug.DEBUG_STARTLEVEL) {
+ Debug.println("SLL: Bundle Startlevel set to " + newSL); //$NON-NLS-1$
+ }
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/SystemBundleActivator.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/SystemBundleActivator.java
new file mode 100644
index 000000000..f3233207d
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/SystemBundleActivator.java
@@ -0,0 +1,110 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.internal.core;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import org.eclipse.osgi.framework.debug.FrameworkDebugOptions;
+import org.eclipse.osgi.internal.resolver.StateImpl;
+import org.eclipse.osgi.service.resolver.State;
+import org.osgi.framework.*;
+import org.osgi.service.condpermadmin.ConditionalPermissionAdmin;
+
+/**
+ * This class activates the System Bundle.
+ */
+
+public class SystemBundleActivator implements BundleActivator {
+ private BundleContext context;
+ private InternalSystemBundle bundle;
+ private Framework framework;
+ private ServiceRegistration<?> packageAdmin;
+ private ServiceRegistration<?> securityAdmin;
+ private ServiceRegistration<?> startLevel;
+ private ServiceRegistration<?> debugOptions;
+ private ServiceRegistration<?> contextFinder;
+
+ public void start(BundleContext bc) throws Exception {
+ this.context = bc;
+ bundle = (InternalSystemBundle) bc.getBundle();
+ framework = bundle.framework;
+
+ if (framework.packageAdmin != null)
+ packageAdmin = register(new String[] {Constants.OSGI_PACKAGEADMIN_NAME}, framework.packageAdmin, null);
+ if (framework.securityAdmin != null)
+ securityAdmin = register(new String[] {Constants.OSGI_PERMISSIONADMIN_NAME, ConditionalPermissionAdmin.class.getName()}, framework.securityAdmin, null);
+ if (framework.startLevelManager != null)
+ startLevel = register(new String[] {Constants.OSGI_STARTLEVEL_NAME}, framework.startLevelManager, null);
+ FrameworkDebugOptions dbgOptions = null;
+ if ((dbgOptions = FrameworkDebugOptions.getDefault()) != null) {
+ dbgOptions.start(bc);
+ debugOptions = register(new String[] {org.eclipse.osgi.service.debug.DebugOptions.class.getName()}, dbgOptions, null);
+ }
+ ClassLoader tccl = framework.getContextFinder();
+ if (tccl != null) {
+ Dictionary<String, Object> props = new Hashtable<String, Object>(7);
+ props.put("equinox.classloader.type", "contextClassLoader"); //$NON-NLS-1$ //$NON-NLS-2$
+ contextFinder = register(new String[] {ClassLoader.class.getName()}, tccl, props);
+ }
+
+ // Always call the adaptor.frameworkStart() at the end of this method.
+ framework.adaptor.frameworkStart(bc);
+ State state = framework.adaptor.getState();
+ if (state instanceof StateImpl)
+ ((StateImpl) state).setResolverHookFactory(new CoreResolverHookFactory((BundleContextImpl) context, framework.getServiceRegistry()));
+ // attempt to resolve all bundles
+ // this is done after the adaptor.frameworkStart has been called
+ // this should be the first time the resolver State is accessed
+ framework.packageAdmin.setResolvedBundles(bundle);
+ // reinitialize the system bundles localization to take into account system bundle fragments
+ framework.systemBundle.manifestLocalization = null;
+ }
+
+ public void stop(BundleContext bc) throws Exception {
+ // Always call the adaptor.frameworkStop() at the begining of this method.
+ framework.adaptor.frameworkStop(bc);
+
+ if (packageAdmin != null)
+ packageAdmin.unregister();
+ if (securityAdmin != null)
+ securityAdmin.unregister();
+ if (startLevel != null)
+ startLevel.unregister();
+ if (debugOptions != null) {
+ FrameworkDebugOptions dbgOptions = FrameworkDebugOptions.getDefault();
+ if (dbgOptions != null)
+ dbgOptions.stop(bc);
+ debugOptions.unregister();
+ }
+ if (contextFinder != null)
+ contextFinder.unregister();
+
+ framework = null;
+ bundle = null;
+ this.context = null;
+ }
+
+ /**
+ * Register a service object.
+ *
+ */
+ private ServiceRegistration<?> register(String[] names, Object service, Dictionary<String, Object> properties) {
+ if (properties == null)
+ properties = new Hashtable<String, Object>(7);
+ Dictionary<String, String> headers = bundle.getHeaders();
+ properties.put(Constants.SERVICE_VENDOR, headers.get(Constants.BUNDLE_VENDOR));
+ properties.put(Constants.SERVICE_RANKING, new Integer(Integer.MAX_VALUE));
+ properties.put(Constants.SERVICE_PID, bundle.getBundleId() + "." + service.getClass().getName()); //$NON-NLS-1$
+ return context.registerService(names, service, properties);
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/UniversalUniqueIdentifier.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/UniversalUniqueIdentifier.java
new file mode 100644
index 000000000..abeab38b1
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/UniversalUniqueIdentifier.java
@@ -0,0 +1,270 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.framework.internal.core;
+
+import java.io.*;
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.security.SecureRandom;
+import java.util.GregorianCalendar;
+import java.util.Random;
+
+public class UniversalUniqueIdentifier {
+
+ /* INSTANCE FIELDS =============================================== */
+
+ private byte[] fBits = new byte[BYTES_SIZE];
+
+ /* NON-FINAL PRIVATE STATIC FIELDS =============================== */
+
+ private volatile static BigInteger fgPreviousClockValue;
+ private volatile static int fgClockAdjustment = 0;
+ private volatile static int fgClockSequence = -1;
+ private final static byte[] nodeAddress;
+
+ static {
+ nodeAddress = computeNodeAddress();
+ }
+
+ /* PRIVATE STATIC FINAL FIELDS =================================== */
+
+ private final static Random fgRandomNumberGenerator = new Random();
+
+ /* PUBLIC STATIC FINAL FIELDS ==================================== */
+
+ public static final int BYTES_SIZE = 16;
+ public static final byte[] UNDEFINED_UUID_BYTES = new byte[16];
+ public static final int MAX_CLOCK_SEQUENCE = 0x4000;
+ public static final int MAX_CLOCK_ADJUSTMENT = 0x7FFF;
+ public static final int TIME_FIELD_START = 0;
+ public static final int TIME_FIELD_STOP = 6;
+ public static final int TIME_HIGH_AND_VERSION = 7;
+ public static final int CLOCK_SEQUENCE_HIGH_AND_RESERVED = 8;
+ public static final int CLOCK_SEQUENCE_LOW = 9;
+ public static final int NODE_ADDRESS_START = 10;
+ public static final int NODE_ADDRESS_BYTE_SIZE = 6;
+
+ public static final int BYTE_MASK = 0xFF;
+
+ public static final int HIGH_NIBBLE_MASK = 0xF0;
+
+ public static final int LOW_NIBBLE_MASK = 0x0F;
+
+ public static final int SHIFT_NIBBLE = 4;
+
+ public static final int ShiftByte = 8;
+
+ /**
+ UniversalUniqueIdentifier default constructor returns a
+ new instance that has been initialized to a unique value.
+ */
+ public UniversalUniqueIdentifier() {
+ this.setVersion(1);
+ this.setVariant(1);
+ this.setTimeValues();
+ this.setNode(getNodeAddress());
+ }
+
+ private void appendByteString(StringBuffer buffer, byte value) {
+ String hexString;
+
+ if (value < 0)
+ hexString = Integer.toHexString(256 + value);
+ else
+ hexString = Integer.toHexString(value);
+ if (hexString.length() == 1)
+ buffer.append("0"); //$NON-NLS-1$
+ buffer.append(hexString);
+ }
+
+ private static BigInteger clockValueNow() {
+ GregorianCalendar now = new GregorianCalendar();
+ BigInteger nowMillis = BigInteger.valueOf(now.getTime().getTime());
+ BigInteger baseMillis = BigInteger.valueOf(now.getGregorianChange().getTime());
+
+ return (nowMillis.subtract(baseMillis).multiply(BigInteger.valueOf(10000L)));
+ }
+
+ /**
+ * Answers the node address attempting to mask the IP
+ * address of this machine.
+ *
+ * @return byte[] the node address
+ */
+ private static byte[] computeNodeAddress() {
+
+ byte[] address = new byte[NODE_ADDRESS_BYTE_SIZE];
+
+ // Seed the secure randomizer with some oft-varying inputs
+ int thread = Thread.currentThread().hashCode();
+ long time = System.currentTimeMillis();
+ int objectId = System.identityHashCode(new String());
+ ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
+ DataOutputStream out = new DataOutputStream(byteOut);
+ byte[] ipAddress = getIPAddress();
+
+ try {
+ if (ipAddress != null)
+ out.write(ipAddress);
+ out.write(thread);
+ out.writeLong(time);
+ out.write(objectId);
+ out.close();
+ } catch (IOException exc) {
+ //ignore the failure, we're just trying to come up with a random seed
+ }
+ byte[] rand = byteOut.toByteArray();
+
+ SecureRandom randomizer = new SecureRandom(rand);
+ randomizer.nextBytes(address);
+
+ // set the MSB of the first octet to 1 to distinguish from IEEE node addresses
+ address[0] = (byte) (address[0] | (byte) 0x80);
+
+ return address;
+ }
+
+ /**
+ Answers the IP address of the local machine using the
+ Java API class <code>InetAddress</code>.
+
+ @return byte[] the network address in network order
+ @see java.net.InetAddress#getLocalHost()
+ @see java.net.InetAddress#getAddress()
+ */
+ private static byte[] getIPAddress() {
+ try {
+ return InetAddress.getLocalHost().getAddress();
+ } catch (UnknownHostException e) {
+ //valid for this to be thrown be a machine with no IP connection
+ //It is VERY important NOT to throw this exception
+ return null;
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // there appears to be a bug in the VM if there is an alias
+ // see bug 354820. As above it is important not to throw this
+ return null;
+ }
+ }
+
+ private static byte[] getNodeAddress() {
+ return nodeAddress;
+ }
+
+ private static int nextClockSequence() {
+
+ if (fgClockSequence == -1)
+ fgClockSequence = (int) (fgRandomNumberGenerator.nextDouble() * MAX_CLOCK_SEQUENCE);
+
+ fgClockSequence = (fgClockSequence + 1) % MAX_CLOCK_SEQUENCE;
+
+ return fgClockSequence;
+ }
+
+ private static BigInteger nextTimestamp() {
+
+ BigInteger timestamp = clockValueNow();
+ int timestampComparison;
+
+ timestampComparison = timestamp.compareTo(fgPreviousClockValue);
+
+ if (timestampComparison == 0) {
+ if (fgClockAdjustment == MAX_CLOCK_ADJUSTMENT) {
+ while (timestamp.compareTo(fgPreviousClockValue) == 0)
+ timestamp = clockValueNow();
+ timestamp = nextTimestamp();
+ } else
+ fgClockAdjustment++;
+ } else {
+ fgClockAdjustment = 0;
+
+ if (timestampComparison < 0)
+ nextClockSequence();
+ }
+
+ return timestamp;
+ }
+
+ private void setClockSequence(int clockSeq) {
+ int clockSeqHigh = (clockSeq >>> ShiftByte) & LOW_NIBBLE_MASK;
+ int reserved = fBits[CLOCK_SEQUENCE_HIGH_AND_RESERVED] & HIGH_NIBBLE_MASK;
+
+ fBits[CLOCK_SEQUENCE_HIGH_AND_RESERVED] = (byte) (reserved | clockSeqHigh);
+ fBits[CLOCK_SEQUENCE_LOW] = (byte) (clockSeq & BYTE_MASK);
+ }
+
+ private void setNode(byte[] bytes) {
+
+ for (int index = 0; index < NODE_ADDRESS_BYTE_SIZE; index++)
+ fBits[index + NODE_ADDRESS_START] = bytes[index];
+ }
+
+ private void setTimestamp(BigInteger timestamp) {
+ BigInteger value = timestamp;
+ BigInteger bigByte = BigInteger.valueOf(256L);
+ BigInteger[] results;
+ int version;
+ int timeHigh;
+
+ for (int index = TIME_FIELD_START; index < TIME_FIELD_STOP; index++) {
+ results = value.divideAndRemainder(bigByte);
+ value = results[0];
+ fBits[index] = (byte) results[1].intValue();
+ }
+ version = fBits[TIME_HIGH_AND_VERSION] & HIGH_NIBBLE_MASK;
+ timeHigh = value.intValue() & LOW_NIBBLE_MASK;
+ fBits[TIME_HIGH_AND_VERSION] = (byte) (timeHigh | version);
+ }
+
+ private synchronized void setTimeValues() {
+ this.setTimestamp(timestamp());
+ this.setClockSequence(fgClockSequence);
+ }
+
+ private int setVariant(int variantIdentifier) {
+ int clockSeqHigh = fBits[CLOCK_SEQUENCE_HIGH_AND_RESERVED] & LOW_NIBBLE_MASK;
+ int variant = variantIdentifier & LOW_NIBBLE_MASK;
+
+ fBits[CLOCK_SEQUENCE_HIGH_AND_RESERVED] = (byte) ((variant << SHIFT_NIBBLE) | clockSeqHigh);
+ return (variant);
+ }
+
+ private void setVersion(int versionIdentifier) {
+ int timeHigh = fBits[TIME_HIGH_AND_VERSION] & LOW_NIBBLE_MASK;
+ int version = versionIdentifier & LOW_NIBBLE_MASK;
+
+ fBits[TIME_HIGH_AND_VERSION] = (byte) (timeHigh | (version << SHIFT_NIBBLE));
+ }
+
+ private static BigInteger timestamp() {
+ BigInteger timestamp;
+
+ if (fgPreviousClockValue == null) {
+ fgClockAdjustment = 0;
+ nextClockSequence();
+ timestamp = clockValueNow();
+ } else
+ timestamp = nextTimestamp();
+
+ fgPreviousClockValue = timestamp;
+ return fgClockAdjustment == 0 ? timestamp : timestamp.add(BigInteger.valueOf(fgClockAdjustment));
+ }
+
+ public String toString() {
+ StringBuffer buffer = new StringBuffer();
+ for (int i = 0; i < fBits.length; i++) {
+ if (i == 4 || i == 6 || i == 8 || i == 10)
+ buffer.append('-');
+ appendByteString(buffer, fBits[i]);
+ }
+ return buffer.toString();
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/Util.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/Util.java
new file mode 100644
index 000000000..63ccfd2d7
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/Util.java
@@ -0,0 +1,208 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.internal.core;
+
+/**
+ * This class contains utility functions.
+ */
+public class Util {
+ /**
+ * Performs a quicksort of the given objects
+ * by their string representation in ascending order.
+ * <p>
+ *
+ * @param array The array of objects to sort
+ */
+ public static void sortByString(Object[] array) {
+ qSortByString(array, 0, array.length - 1);
+ }
+
+ /**
+ * Sorts the array of objects by their string representation
+ * in ascending order.
+ * <p>
+ * This is a version of C.A.R Hoare's Quick Sort algorithm.
+ *
+ * @param array the array of objects to sort
+ * @param start the start index to begin sorting
+ * @param stop the end index to stop sorting
+ *
+ * @exception ArrayIndexOutOfBoundsException when <code>start < 0</code>
+ * or <code>end >= array.length</code>
+ */
+ public static void qSortByString(Object[] array, int start, int stop) {
+ if (start >= stop)
+ return;
+
+ int left = start; // left index
+ int right = stop; // right index
+ Object temp; // for swapping
+
+ // arbitrarily establish a partition element as the midpoint of the array
+ String mid = String.valueOf(array[(start + stop) / 2]);
+
+ // loop through the array until indices cross
+ while (left <= right) {
+ // find the first element that is smaller than the partition element from the left
+ while ((left < stop) && (String.valueOf(array[left]).compareTo(mid) < 0)) {
+ ++left;
+ }
+ // find an element that is smaller than the partition element from the right
+ while ((right > start) && (mid.compareTo(String.valueOf(array[right])) < 0)) {
+ --right;
+ }
+ // if the indices have not crossed, swap
+ if (left <= right) {
+ temp = array[left];
+ array[left] = array[right];
+ array[right] = temp;
+ ++left;
+ --right;
+ }
+ }
+ // sort the left partition, if the right index has not reached the left side of array
+ if (start < right) {
+ qSortByString(array, start, right);
+ }
+ // sort the right partition, if the left index has not reached the right side of array
+ if (left < stop) {
+ qSortByString(array, left, stop);
+ }
+ }
+
+ /**
+ * Sorts the specified range in the array in ascending order.
+ *
+ * @param array the Object array to be sorted
+ * @param start the start index to sort
+ * @param end the last + 1 index to sort
+ *
+ * @exception ClassCastException when an element in the array does not
+ * implement Comparable or elements cannot be compared to each other
+ * @exception IllegalArgumentException when <code>start > end</code>
+ * @exception ArrayIndexOutOfBoundsException when <code>start < 0</code>
+ * or <code>end > array.size()</code>
+ */
+ @SuppressWarnings("unchecked")
+ public static void sort(Object[] array, int start, int end) {
+ int middle = (start + end) / 2;
+ if (start + 1 < middle)
+ sort(array, start, middle);
+ if (middle + 1 < end)
+ sort(array, middle, end);
+ if (start + 1 >= end)
+ return; // this case can only happen when this method is called by the user
+ if (((Comparable<Object>) array[middle - 1]).compareTo(array[middle]) <= 0)
+ return;
+ if (start + 2 == end) {
+ Object temp = array[start];
+ array[start] = array[middle];
+ array[middle] = temp;
+ return;
+ }
+ int i1 = start, i2 = middle, i3 = 0;
+ Object[] merge = new Object[end - start];
+ while (i1 < middle && i2 < end) {
+ merge[i3++] = ((Comparable<Object>) array[i1]).compareTo(array[i2]) <= 0 ? array[i1++] : array[i2++];
+ }
+ if (i1 < middle)
+ System.arraycopy(array, i1, merge, i3, middle - i1);
+ System.arraycopy(merge, 0, array, start, i2 - start);
+ }
+
+ /**
+ * Sorts the specified range in the array in descending order.
+ *
+ * @param array the Object array to be sorted
+ * @param start the start index to sort
+ * @param end the last + 1 index to sort
+ *
+ * @exception ClassCastException when an element in the array does not
+ * implement Comparable or elements cannot be compared to each other
+ * @exception IllegalArgumentException when <code>start > end</code>
+ * @exception ArrayIndexOutOfBoundsException when <code>start < 0</code>
+ * or <code>end > array.size()</code>
+ */
+ public static void dsort(Object[] array, int start, int end) {
+ // first sort in ascending order
+ sort(array, start, end);
+ // then swap the elements in the array
+ swap(array);
+ }
+
+ /**
+ * Reverse the elements in the array.
+ *
+ * @param array the Object array to be reversed
+ */
+ public static void swap(Object[] array) {
+ int start = 0;
+ int end = array.length - 1;
+ while (start < end) {
+ Object temp = array[start];
+ array[start++] = array[end];
+ array[end--] = temp;
+ }
+ }
+
+ /**
+ * Returns a string representation of the object
+ * in the given length.
+ * If the string representation of the given object
+ * is longer then it is truncated.
+ * If it is shorter then it is padded with the blanks
+ * to the given total length.
+ * If the given object is a number then the padding
+ * is done on the left, otherwise on the right.
+ *
+ * @param object the object to convert
+ * @param length the length the output string
+ */
+ public static String toString(Object object, int length) {
+ boolean onLeft = object instanceof Number;
+ return toString(object, length, ' ', onLeft);
+ }
+
+ /**
+ * Returns a string representation of the object
+ * in the given length.
+ * If the string representation of the given object
+ * is longer then it is truncated.
+ * If it is shorter then it is padded to the left or right
+ * with the given character to the given total length.
+ *
+ * @param object the object to convert
+ * @param length the length the output string
+ * @param pad the pad character
+ * @param onLeft if <code>true</code> pad on the left, otherwise an the right
+ */
+ public static String toString(Object object, int length, char pad, boolean onLeft) {
+ String input = String.valueOf(object);
+ int size = input.length();
+ if (size >= length) {
+ int start = (onLeft) ? size - length : 0;
+ return input.substring(start, length);
+ }
+
+ StringBuffer padding = new StringBuffer(length - size);
+ for (int i = size; i < length; i++)
+ padding.append(pad);
+
+ StringBuffer stringBuffer = new StringBuffer(length);
+ if (onLeft)
+ stringBuffer.append(padding.toString());
+ stringBuffer.append(input);
+ if (!onLeft)
+ stringBuffer.append(padding.toString());
+ return stringBuffer.toString();
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/osname.aliases b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/osname.aliases
new file mode 100644
index 000000000..0aff94e32
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/osname.aliases
@@ -0,0 +1,46 @@
+########################################################################
+# Copyright (c) 2003, 2012 IBM Corporation and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Contributors:
+# IBM Corporation - initial API and implementation
+########################################################################
+
+# This file must be UTF8 encoded.
+
+#OS Aliases Description
+
+AIX # IBM
+DigitalUnix # Compaq
+embos # Segger Embedded Software Solutions
+Epoc32 SymbianOS # Symbian OS
+FreeBSD # Free BSD
+HPUX # Hewlett Packard
+IRIX # Sillicon Graphics
+Linux # Open source
+MacOS "Mac OS" # Apple
+MacOSX "Mac OS X" # Apple
+NetBSD # Open source
+Netware # Novell
+OpenBSD # Open source
+OS2 OS/2 # IBM
+QNX procnto # QNX Neutrino 2.1
+Solaris # Sun
+SunOS # Sun
+VxWorks # WindRiver Systems
+Windows95 "Windows 95" Win95 Win32 # Microsoft
+Windows98 "Windows 98" Win98 Win32 # Microsoft
+WindowsNT "Windows NT" WinNT Win32 # Microsoft
+WindowsCE "Windows CE" WinCE # Microsoft
+Windows2000 "Windows 2000" Win2000 Win32 # Microsoft
+WindowsXP "Windows XP" WinXP Win32 # Microsoft
+Windows2003 "Windows 2003" "Windows Server 2003" Win2003 Win32 # Microsoft
+WindowsVista WinVista "Windows Vista" Win32 # Microsoft
+Windows2008 "Windows 2008" "Windows Server 2008" Win2008 Win32 # Microsoft
+WindowsServer2008 "Windows 2008" "Windows Server 2008" Win2008 Win32 # Microsoft
+WindowsServer2008R2 "Windows 2008 R2" "Windows Server 2008 R2" Win2008R2 Win32 # Microsoft
+Windows7 "Windows 7" Win7 Win32 # Microsoft
+Windows8 "Windows 8" Win8 Win32 # Microsoft \ No newline at end of file
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/processor.aliases b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/processor.aliases
new file mode 100644
index 000000000..5fb0ff398
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/processor.aliases
@@ -0,0 +1,28 @@
+########################################################################
+# Copyright (c) 2003, 2005 IBM Corporation and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Contributors:
+# IBM Corporation - initial API and implementation
+########################################################################
+
+# This file must be UTF8 encoded.
+
+#Processor Aliases Description
+
+68k # Motorola 68000 and up
+ARM # Intel Strong ARM
+Alpha # Compaq (ex DEC)
+Ignite psc1k # PTSC
+Mips # SGI
+PArisc # Hewlett Packard PA Risc
+PowerPC power ppc # Motorola/IBM Power PC
+Sparc # SUN
+x86 pentium i386 i486 i586 i686 # Intel
+s390 # IBM System 390
+s390x # IBM System 390 (64-bit)
+v850e # NEC V850E
+x86-64 amd64 em64t x86_64 # 64 bit x86 architecture
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/ContentHandlerFactory.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/ContentHandlerFactory.java
new file mode 100644
index 000000000..c84f76da1
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/ContentHandlerFactory.java
@@ -0,0 +1,163 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.internal.protocol;
+
+import java.lang.reflect.Method;
+import java.net.ContentHandler;
+import java.net.URLConnection;
+import java.util.*;
+import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor;
+import org.eclipse.osgi.framework.internal.core.Msg;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.url.URLConstants;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * The ContentHandlerFactory is registered with the JVM to provide content handlers
+ * to requestors. The ContentHandlerFactory will first look for built-in content handlers.
+ * If a built in handler exists, this factory will return null. Otherwise, this ContentHandlerFactory
+ * will search the service registry for a maching Content-Handler and, if found, return a
+ * proxy for that content handler.
+ */
+// TODO rename this class!!! its really confusing to name the impl the same as the interface
+public class ContentHandlerFactory extends MultiplexingFactory implements java.net.ContentHandlerFactory {
+ private ServiceTracker<ContentHandler, ContentHandler> contentHandlerTracker;
+
+ private static final String contentHandlerClazz = "java.net.ContentHandler"; //$NON-NLS-1$
+ private static final String CONTENT_HANDLER_PKGS = "java.content.handler.pkgs"; //$NON-NLS-1$
+ private static final String DEFAULT_VM_CONTENT_HANDLERS = "sun.net.www.content"; //$NON-NLS-1$
+
+ private static final List<Class<?>> ignoredClasses = Arrays.asList(new Class<?>[] {MultiplexingContentHandler.class, ContentHandlerFactory.class, URLConnection.class});
+
+ private Map<String, ContentHandlerProxy> proxies;
+ private java.net.ContentHandlerFactory parentFactory;
+
+ public ContentHandlerFactory(BundleContext context, FrameworkAdaptor adaptor) {
+ super(context, adaptor);
+
+ proxies = new Hashtable<String, ContentHandlerProxy>(5);
+
+ //We need to track content handler registrations
+ contentHandlerTracker = new ServiceTracker<ContentHandler, ContentHandler>(context, contentHandlerClazz, null);
+ contentHandlerTracker.open();
+ }
+
+ /**
+ * @see java.net.ContentHandlerFactory#createContentHandler(String)
+ */
+ //TODO method is too long... consider reducing indentation (returning quickly) and moving complex steps to private methods
+ public ContentHandler createContentHandler(String contentType) {
+ //first, we check to see if there exists a built in content handler for
+ //this content type. we can not overwrite built in ContentHandlers
+ String builtInHandlers = StreamHandlerFactory.secureAction.getProperty(CONTENT_HANDLER_PKGS);
+ builtInHandlers = builtInHandlers == null ? DEFAULT_VM_CONTENT_HANDLERS : DEFAULT_VM_CONTENT_HANDLERS + '|' + builtInHandlers;
+ Class<?> clazz = null;
+ if (builtInHandlers != null) {
+ //replace '/' with a '.' and all characters not allowed in a java class name
+ //with a '_'.
+
+ // find all characters not allowed in java names
+ String convertedContentType = contentType.replace('.', '_');
+ convertedContentType = convertedContentType.replace('/', '.');
+ convertedContentType = convertedContentType.replace('-', '_');
+ StringTokenizer tok = new StringTokenizer(builtInHandlers, "|"); //$NON-NLS-1$
+ while (tok.hasMoreElements()) {
+ StringBuffer name = new StringBuffer();
+ name.append(tok.nextToken());
+ name.append("."); //$NON-NLS-1$
+ name.append(convertedContentType);
+ try {
+ clazz = StreamHandlerFactory.secureAction.loadSystemClass(name.toString());
+ if (clazz != null) {
+ return (null); //this class exists, it is a built in handler, let the JVM handle it
+ }
+ } catch (ClassNotFoundException ex) {
+ //keep looking
+ }
+ }
+ }
+
+ if (isMultiplexing())
+ return new MultiplexingContentHandler(contentType, this);
+
+ return createInternalContentHandler(contentType);
+ }
+
+ public ContentHandler createInternalContentHandler(String contentType) {
+ //first check to see if the handler is in the cache
+ ContentHandlerProxy proxy = proxies.get(contentType);
+ if (proxy != null) {
+ return (proxy);
+ }
+ ServiceReference<ContentHandler>[] serviceReferences = contentHandlerTracker.getServiceReferences();
+ if (serviceReferences != null) {
+ for (int i = 0; i < serviceReferences.length; i++) {
+ Object prop = serviceReferences[i].getProperty(URLConstants.URL_CONTENT_MIMETYPE);
+ if (prop instanceof String)
+ prop = new String[] {(String) prop}; // TODO should this be a warning?
+ if (!(prop instanceof String[])) {
+ String message = NLS.bind(Msg.URL_HANDLER_INCORRECT_TYPE, new Object[] {URLConstants.URL_CONTENT_MIMETYPE, contentHandlerClazz, serviceReferences[i].getBundle()});
+ adaptor.getFrameworkLog().log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.WARNING, 0, message, 0, null, null));
+ continue;
+ }
+ String[] contentHandler = (String[]) prop;
+ for (int j = 0; j < contentHandler.length; j++) {
+ if (contentHandler[j].equals(contentType)) {
+ proxy = new ContentHandlerProxy(contentType, serviceReferences[i], context);
+ proxies.put(contentType, proxy);
+ return (proxy);
+ }
+ }
+ }
+ }
+ // if parent is present do parent lookup before returning a proxy
+ if (parentFactory != null) {
+ ContentHandler parentHandler = parentFactory.createContentHandler(contentType);
+ if (parentHandler != null)
+ return parentHandler;
+ }
+ //If we can't find the content handler in the service registry, return Proxy with DefaultContentHandler set.
+ //We need to do this because if we return null, we won't get called again for this content type.
+ proxy = new ContentHandlerProxy(contentType, null, context);
+ proxies.put(contentType, proxy);
+ return (proxy);
+ }
+
+ public synchronized ContentHandler findAuthorizedContentHandler(String contentType) {
+ Object factory = findAuthorizedFactory(ignoredClasses);
+ if (factory == null)
+ return null;
+
+ if (factory == this)
+ return createInternalContentHandler(contentType);
+
+ try {
+ Method createInternalContentHandlerMethod = factory.getClass().getMethod("createInternalContentHandler", new Class[] {String.class}); //$NON-NLS-1$
+ return (ContentHandler) createInternalContentHandlerMethod.invoke(factory, new Object[] {contentType});
+ } catch (Exception e) {
+ adaptor.getFrameworkLog().log(new FrameworkLogEntry(ContentHandlerFactory.class.getName(), FrameworkLogEntry.ERROR, 0, "findAuthorizedContentHandler-loop", 0, e, null)); //$NON-NLS-1$
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+
+ public Object getParentFactory() {
+ return parentFactory;
+ }
+
+ public void setParentFactory(Object parentFactory) {
+ if (this.parentFactory == null) // only allow it to be set once
+ this.parentFactory = (java.net.ContentHandlerFactory) parentFactory;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/ContentHandlerProxy.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/ContentHandlerProxy.java
new file mode 100644
index 000000000..b60b7940c
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/ContentHandlerProxy.java
@@ -0,0 +1,152 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.internal.protocol;
+
+import java.io.IOException;
+import java.net.ContentHandler;
+import java.net.URLConnection;
+import org.osgi.framework.*;
+import org.osgi.service.url.URLConstants;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+
+/**
+ * The ContentHandlerProxy is a ContentHandler that acts as a proxy for registered ContentHandlers.
+ * When a ContentHandler is requested from the ContentHandlerFactory and it exists in the service
+ * registry, a ContentHandlerProxy is created which will pass all the requests from the requestor to
+ * the real ContentHandler. We can't return the real ContentHandler from the ContentHandlerFactory
+ * because the JVM caches ContentHandlers and therefore would not support a dynamic environment of
+ * ContentHandlers being registered and unregistered.
+ */
+public class ContentHandlerProxy extends ContentHandler implements ServiceTrackerCustomizer<ContentHandler, ServiceReference<ContentHandler>> {
+ protected ContentHandler realHandler;
+
+ //TODO avoid type-based names
+ protected ServiceTracker<ContentHandler, ServiceReference<ContentHandler>> contentHandlerServiceTracker;
+
+ protected BundleContext context;
+ protected ServiceReference<ContentHandler> contentHandlerServiceReference;
+
+ protected String contentType;
+
+ protected int ranking = Integer.MIN_VALUE;
+
+ public ContentHandlerProxy(String contentType, ServiceReference<ContentHandler> reference, BundleContext context) {
+ this.context = context;
+ this.contentType = contentType;
+
+ // In case the reference == null, the proxy is constructed with DefaultContentHandler for a Content Handler
+ // until a real ContentHandler for this mime-type is registered
+ setNewHandler(reference, getRank(reference));
+
+ contentHandlerServiceTracker = new ServiceTracker<ContentHandler, ServiceReference<ContentHandler>>(context, ContentHandler.class.getName(), this);
+ StreamHandlerFactory.secureAction.open(contentHandlerServiceTracker);
+ }
+
+ private void setNewHandler(ServiceReference<ContentHandler> reference, int rank) {
+ if (contentHandlerServiceReference != null)
+ context.ungetService(contentHandlerServiceReference);
+
+ contentHandlerServiceReference = reference;
+ ranking = rank;
+
+ if (reference == null)
+ realHandler = new DefaultContentHandler();
+ else
+ realHandler = StreamHandlerFactory.secureAction.getService(reference, context);
+ }
+
+ /**
+ * @see org.osgi.util.tracker.ServiceTrackerCustomizer#addingService(ServiceReference)
+ */
+ public ServiceReference<ContentHandler> addingService(ServiceReference<ContentHandler> reference) {
+ //check to see if our contentType is being registered by another service
+ Object prop = reference.getProperty(URLConstants.URL_CONTENT_MIMETYPE);
+ if (!(prop instanceof String[]))
+ return null;
+ String[] contentTypes = (String[]) prop;
+ for (int i = 0; i < contentTypes.length; i++) {
+ if (contentTypes[i].equals(contentType)) {
+ //If our contentType is registered by another service, check the service ranking and switch URLStreamHandlers if nessecary.
+ int newServiceRanking = getRank(reference);
+ if (newServiceRanking > ranking || contentHandlerServiceReference == null)
+ setNewHandler(reference, newServiceRanking);
+ return (reference);
+ }
+ }
+
+ //we don't want to continue hearing events about a ContentHandler service not registered under our contentType
+ return (null);
+ }
+
+ /**
+ * @see org.osgi.util.tracker.ServiceTrackerCustomizer#modifiedService(ServiceReference, Object)
+ */
+
+ public void modifiedService(ServiceReference<ContentHandler> reference, ServiceReference<ContentHandler> service) {
+ int newrank = getRank(reference);
+ if (reference == contentHandlerServiceReference) {
+ if (newrank < ranking) {
+ // The ContentHandler we are currently using has dropped it's ranking below a ContentHandler
+ // registered for the same protocol. We need to swap out ContentHandlers.
+ // this should get us the highest ranked service, if available
+ ServiceReference<ContentHandler> newReference = contentHandlerServiceTracker.getServiceReference();
+ if (newReference != contentHandlerServiceReference && newReference != null) {
+ setNewHandler(newReference, ((Integer) newReference.getProperty(Constants.SERVICE_RANKING)).intValue());
+ }
+ }
+ } else if (newrank > ranking) {
+ // the service changed is another URLHandler that we are not currently using
+ // If it's ranking is higher, we must swap it in.
+ setNewHandler(reference, newrank);
+ }
+ }
+
+ /**
+ * @see org.osgi.util.tracker.ServiceTrackerCustomizer#removedService(ServiceReference, Object)
+ */
+ public void removedService(ServiceReference<ContentHandler> reference, ServiceReference<ContentHandler> service) {
+ //check to see if our URLStreamHandler was unregistered.
+ if (reference != contentHandlerServiceReference)
+ return;
+ // If so, look for a lower ranking URLHandler
+ // this should get us the highest ranking service left, if available
+ ServiceReference<ContentHandler> newReference = contentHandlerServiceTracker.getServiceReference();
+ // if newReference == null then we will use the DefaultContentHandler here
+ setNewHandler(newReference, getRank(newReference));
+ }
+
+ /**
+ * @see java.net.ContentHandler#getContent(URLConnection)
+ */
+
+ public Object getContent(URLConnection uConn) throws IOException {
+ return realHandler.getContent(uConn);
+ }
+
+ private int getRank(ServiceReference<?> reference) {
+ if (reference == null)
+ return Integer.MIN_VALUE;
+ Object property = reference.getProperty(Constants.SERVICE_RANKING);
+ return (property instanceof Integer) ? ((Integer) property).intValue() : 0;
+ }
+
+ class DefaultContentHandler extends ContentHandler {
+
+ /**
+ * @see java.net.ContentHandler#getContent(URLConnection)
+ */
+ public Object getContent(URLConnection uConn) throws IOException {
+ return uConn.getInputStream();
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/MultiplexingContentHandler.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/MultiplexingContentHandler.java
new file mode 100644
index 000000000..3a49caa9f
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/MultiplexingContentHandler.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2006 Cognos Incorporated, IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ *******************************************************************************/
+package org.eclipse.osgi.framework.internal.protocol;
+
+import java.io.IOException;
+import java.net.ContentHandler;
+import java.net.URLConnection;
+
+public class MultiplexingContentHandler extends ContentHandler {
+
+ private String contentType;
+ private ContentHandlerFactory factory;
+
+ public MultiplexingContentHandler(String contentType, ContentHandlerFactory factory) {
+ this.contentType = contentType;
+ this.factory = factory;
+ }
+
+ public Object getContent(URLConnection uConn) throws IOException {
+ ContentHandler handler = factory.findAuthorizedContentHandler(contentType);
+ if (handler != null)
+ return handler.getContent(uConn);
+
+ return uConn.getInputStream();
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/MultiplexingFactory.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/MultiplexingFactory.java
new file mode 100644
index 000000000..648292324
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/MultiplexingFactory.java
@@ -0,0 +1,173 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2010 Cognos Incorporated, IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ *******************************************************************************/
+package org.eclipse.osgi.framework.internal.protocol;
+
+import java.lang.reflect.Method;
+import java.util.LinkedList;
+import java.util.List;
+import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.packageadmin.PackageAdmin;
+import org.osgi.util.tracker.ServiceTracker;
+
+/*
+ * An abstract class for handler factory impls (Stream and Content) that can
+ * handle environments running multiple osgi frameworks with the same VM.
+ */
+public abstract class MultiplexingFactory {
+
+ protected static final String PACKAGEADMINCLASS = "org.osgi.service.packageadmin.PackageAdmin"; //$NON-NLS-1$
+ protected BundleContext context;
+ protected FrameworkAdaptor adaptor;
+ private List<Object> factories; // list of multiplexed factories
+ private ServiceTracker<ServiceReference<?>, PackageAdmin> packageAdminTracker;
+
+ // used to get access to the protected SecurityManager#getClassContext method
+ static class InternalSecurityManager extends SecurityManager {
+ public Class<?>[] getClassContext() {
+ return super.getClassContext();
+ }
+ }
+
+ private static InternalSecurityManager internalSecurityManager = new InternalSecurityManager();
+
+ MultiplexingFactory(BundleContext context, FrameworkAdaptor adaptor) {
+ this.context = context;
+ this.adaptor = adaptor;
+ packageAdminTracker = new ServiceTracker<ServiceReference<?>, PackageAdmin>(context, PACKAGEADMINCLASS, null);
+ packageAdminTracker.open();
+ }
+
+ abstract public void setParentFactory(Object parentFactory);
+
+ abstract public Object getParentFactory();
+
+ public boolean isMultiplexing() {
+ return getFactories() != null;
+ }
+
+ public void register(Object factory) {
+ // set parent for each factory so they can do proper delegation
+ try {
+ Class<?> clazz = factory.getClass();
+ Method setParentFactory = clazz.getMethod("setParentFactory", new Class[] {Object.class}); //$NON-NLS-1$
+ setParentFactory.invoke(factory, new Object[] {getParentFactory()});
+ } catch (Exception e) {
+ adaptor.getFrameworkLog().log(new FrameworkLogEntry(MultiplexingFactory.class.getName(), FrameworkLogEntry.ERROR, 0, "register", FrameworkLogEntry.ERROR, e, null)); //$NON-NLS-1$
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ addFactory(factory);
+ }
+
+ public void unregister(Object factory) {
+ removeFactory(factory);
+ // close the service tracker
+ try {
+ // this is brittle; if class does not directly extend MultplexingFactory then this method will not exist, but we do not want a public method here
+ Method closeTracker = factory.getClass().getSuperclass().getDeclaredMethod("closePackageAdminTracker", (Class[]) null); //$NON-NLS-1$
+ closeTracker.setAccessible(true); // its a private method
+ closeTracker.invoke(factory, (Object[]) null);
+ } catch (Exception e) {
+ adaptor.getFrameworkLog().log(new FrameworkLogEntry(MultiplexingFactory.class.getName(), FrameworkLogEntry.ERROR, 0, "unregister", FrameworkLogEntry.ERROR, e, null)); //$NON-NLS-1$
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+
+ public Object designateSuccessor() {
+ List<Object> released = releaseFactories();
+ // Note that we do this outside of the sync block above.
+ // This is only possible because we do additional locking outside of
+ // this class to ensure no other threads are trying to manipulate the
+ // list of registered factories. See Framework class the following methods:
+ // Framework.installURLStreamHandlerFactory(BundleContext, FrameworkAdaptor)
+ // Framework.installContentHandlerFactory(BundleContext, FrameworkAdaptor)
+ // Framework.uninstallURLStreamHandlerFactory
+ // Framework.uninstallContentHandlerFactory()
+ if (released == null || released.isEmpty())
+ return getParentFactory();
+ Object successor = released.remove(0);
+ try {
+ Class<?> clazz = successor.getClass();
+ Method register = clazz.getMethod("register", new Class[] {Object.class}); //$NON-NLS-1$
+ for (Object r : released) {
+ register.invoke(successor, new Object[] {r});
+ }
+ } catch (Exception e) {
+ adaptor.getFrameworkLog().log(new FrameworkLogEntry(MultiplexingFactory.class.getName(), FrameworkLogEntry.ERROR, 0, "designateSuccessor", FrameworkLogEntry.ERROR, e, null)); //$NON-NLS-1$
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ closePackageAdminTracker(); // close tracker
+ return successor;
+ }
+
+ private void closePackageAdminTracker() {
+ packageAdminTracker.close();
+ }
+
+ public Object findAuthorizedFactory(List<Class<?>> ignoredClasses) {
+ List<Object> current = getFactories();
+ Class<?>[] classStack = internalSecurityManager.getClassContext();
+ for (int i = 0; i < classStack.length; i++) {
+ Class<?> clazz = classStack[i];
+ if (clazz == InternalSecurityManager.class || clazz == MultiplexingFactory.class || ignoredClasses.contains(clazz))
+ continue;
+ if (hasAuthority(clazz))
+ return this;
+ if (current == null)
+ continue;
+ for (Object factory : current) {
+ try {
+ Method hasAuthorityMethod = factory.getClass().getMethod("hasAuthority", new Class[] {Class.class}); //$NON-NLS-1$
+ if (((Boolean) hasAuthorityMethod.invoke(factory, new Object[] {clazz})).booleanValue()) {
+ return factory;
+ }
+ } catch (Exception e) {
+ adaptor.getFrameworkLog().log(new FrameworkLogEntry(MultiplexingFactory.class.getName(), FrameworkLogEntry.ERROR, 0, "findAuthorizedURLStreamHandler-loop", FrameworkLogEntry.ERROR, e, null)); //$NON-NLS-1$
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+ }
+ return null;
+ }
+
+ public boolean hasAuthority(Class<?> clazz) {
+ PackageAdmin packageAdminService = packageAdminTracker.getService();
+ if (packageAdminService != null) {
+ return packageAdminService.getBundle(clazz) != null;
+ }
+ return false;
+ }
+
+ private synchronized List<Object> getFactories() {
+ return factories;
+ }
+
+ private synchronized List<Object> releaseFactories() {
+ if (factories == null)
+ return null;
+
+ List<Object> released = new LinkedList<Object>(factories);
+ factories = null;
+ return released;
+ }
+
+ private synchronized void addFactory(Object factory) {
+ List<Object> updated = (factories == null) ? new LinkedList<Object>() : new LinkedList<Object>(factories);
+ updated.add(factory);
+ factories = updated;
+ }
+
+ private synchronized void removeFactory(Object factory) {
+ List<Object> updated = new LinkedList<Object>(factories);
+ updated.remove(factory);
+ factories = updated.isEmpty() ? null : updated;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/MultiplexingURLStreamHandler.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/MultiplexingURLStreamHandler.java
new file mode 100644
index 000000000..4bc71a844
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/MultiplexingURLStreamHandler.java
@@ -0,0 +1,247 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2010 Cognos Incorporated, IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ *******************************************************************************/
+package org.eclipse.osgi.framework.internal.protocol;
+
+import java.io.IOException;
+import java.lang.reflect.*;
+import java.net.*;
+import org.eclipse.osgi.framework.internal.core.Framework;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+
+public class MultiplexingURLStreamHandler extends URLStreamHandler {
+ private static Method openConnectionMethod;
+ private static Method equalsMethod;
+ private static Method getDefaultPortMethod;
+ private static Method getHostAddressMethod;
+ private static Method hashCodeMethod;
+ private static Method hostsEqualMethod;
+ private static Method parseURLMethod;
+ private static Method sameFileMethod;
+ private static Method setURLMethod;
+ private static Method toExternalFormMethod;
+ private static Field handlerField;
+ private static boolean methodsInitialized = false;
+
+ private String protocol;
+ private StreamHandlerFactory factory;
+
+ private static synchronized void initializeMethods(StreamHandlerFactory factory) {
+ if (methodsInitialized)
+ return;
+ try {
+ openConnectionMethod = URLStreamHandler.class.getDeclaredMethod("openConnection", new Class[] {URL.class}); //$NON-NLS-1$
+ openConnectionMethod.setAccessible(true);
+
+ equalsMethod = URLStreamHandler.class.getDeclaredMethod("equals", new Class[] {URL.class, URL.class}); //$NON-NLS-1$
+ equalsMethod.setAccessible(true);
+
+ getDefaultPortMethod = URLStreamHandler.class.getDeclaredMethod("getDefaultPort", (Class[]) null); //$NON-NLS-1$
+ getDefaultPortMethod.setAccessible(true);
+
+ getHostAddressMethod = URLStreamHandler.class.getDeclaredMethod("getHostAddress", new Class[] {URL.class}); //$NON-NLS-1$
+ getHostAddressMethod.setAccessible(true);
+
+ hashCodeMethod = URLStreamHandler.class.getDeclaredMethod("hashCode", new Class[] {URL.class}); //$NON-NLS-1$
+ hashCodeMethod.setAccessible(true);
+
+ hostsEqualMethod = URLStreamHandler.class.getDeclaredMethod("hostsEqual", new Class[] {URL.class, URL.class}); //$NON-NLS-1$
+ hostsEqualMethod.setAccessible(true);
+
+ parseURLMethod = URLStreamHandler.class.getDeclaredMethod("parseURL", new Class[] {URL.class, String.class, Integer.TYPE, Integer.TYPE}); //$NON-NLS-1$
+ parseURLMethod.setAccessible(true);
+
+ sameFileMethod = URLStreamHandler.class.getDeclaredMethod("sameFile", new Class[] {URL.class, URL.class}); //$NON-NLS-1$
+ sameFileMethod.setAccessible(true);
+
+ setURLMethod = URLStreamHandler.class.getDeclaredMethod("setURL", new Class[] {URL.class, String.class, String.class, Integer.TYPE, String.class, String.class, String.class, String.class, String.class}); //$NON-NLS-1$
+ setURLMethod.setAccessible(true);
+
+ toExternalFormMethod = URLStreamHandler.class.getDeclaredMethod("toExternalForm", new Class[] {URL.class}); //$NON-NLS-1$
+ toExternalFormMethod.setAccessible(true);
+
+ try {
+ handlerField = URL.class.getDeclaredField("handler"); //$NON-NLS-1$
+ } catch (NoSuchFieldException e) {
+ handlerField = Framework.getField(URL.class, URLStreamHandler.class, true);
+ if (handlerField == null)
+ throw e;
+ }
+ handlerField.setAccessible(true);
+ } catch (Exception e) {
+ factory.adaptor.getFrameworkLog().log(new FrameworkLogEntry(MultiplexingURLStreamHandler.class.getName(), FrameworkLogEntry.ERROR, 0, "initializeMethods", 0, e, null)); //$NON-NLS-1$
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ methodsInitialized = true;
+ }
+
+ public MultiplexingURLStreamHandler(String protocol, StreamHandlerFactory factory) {
+ this.protocol = protocol;
+ this.factory = factory;
+ initializeMethods(factory);
+ }
+
+ protected URLConnection openConnection(URL url) throws IOException {
+ URLStreamHandler handler = factory.findAuthorizedURLStreamHandler(protocol);
+ if (handler != null) {
+ try {
+ return (URLConnection) openConnectionMethod.invoke(handler, new Object[] {url});
+ } catch (InvocationTargetException e) {
+ if (e.getTargetException() instanceof IOException)
+ throw (IOException) e.getTargetException();
+ throw (RuntimeException) e.getTargetException();
+ } catch (Exception e) {
+ factory.adaptor.getFrameworkLog().log(new FrameworkLogEntry(MultiplexingURLStreamHandler.class.getName(), FrameworkLogEntry.ERROR, 0, "openConnection", 0, e, null)); //$NON-NLS-1$
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+ throw new MalformedURLException();
+ }
+
+ protected boolean equals(URL url1, URL url2) {
+ URLStreamHandler handler = factory.findAuthorizedURLStreamHandler(protocol);
+ if (handler != null) {
+ try {
+ return ((Boolean) equalsMethod.invoke(handler, new Object[] {url1, url2})).booleanValue();
+ } catch (InvocationTargetException e) {
+ throw (RuntimeException) e.getTargetException();
+ } catch (Exception e) {
+ factory.adaptor.getFrameworkLog().log(new FrameworkLogEntry(MultiplexingURLStreamHandler.class.getName(), FrameworkLogEntry.ERROR, 0, "equals", 0, e, null)); //$NON-NLS-1$
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+ throw new IllegalStateException();
+ }
+
+ protected int getDefaultPort() {
+ URLStreamHandler handler = factory.findAuthorizedURLStreamHandler(protocol);
+ if (handler != null) {
+ try {
+ return ((Integer) getDefaultPortMethod.invoke(handler, (Object[]) null)).intValue();
+ } catch (InvocationTargetException e) {
+ throw (RuntimeException) e.getTargetException();
+ } catch (Exception e) {
+ factory.adaptor.getFrameworkLog().log(new FrameworkLogEntry(MultiplexingURLStreamHandler.class.getName(), FrameworkLogEntry.ERROR, 0, "getDefaultPort", 0, e, null)); //$NON-NLS-1$
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+ throw new IllegalStateException();
+ }
+
+ protected InetAddress getHostAddress(URL url) {
+ URLStreamHandler handler = factory.findAuthorizedURLStreamHandler(protocol);
+ if (handler != null) {
+ try {
+ return (InetAddress) getHostAddressMethod.invoke(handler, new Object[] {url});
+ } catch (InvocationTargetException e) {
+ throw (RuntimeException) e.getTargetException();
+ } catch (Exception e) {
+ factory.adaptor.getFrameworkLog().log(new FrameworkLogEntry(MultiplexingURLStreamHandler.class.getName(), FrameworkLogEntry.ERROR, 0, "hashCode", 0, e, null)); //$NON-NLS-1$
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+ throw new IllegalStateException();
+ }
+
+ protected int hashCode(URL url) {
+ URLStreamHandler handler = factory.findAuthorizedURLStreamHandler(protocol);
+ if (handler != null) {
+ try {
+ return ((Integer) hashCodeMethod.invoke(handler, new Object[] {url})).intValue();
+ } catch (InvocationTargetException e) {
+ throw (RuntimeException) e.getTargetException();
+ } catch (Exception e) {
+ factory.adaptor.getFrameworkLog().log(new FrameworkLogEntry(MultiplexingURLStreamHandler.class.getName(), FrameworkLogEntry.ERROR, 0, "hashCode", 0, e, null)); //$NON-NLS-1$
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+ throw new IllegalStateException();
+ }
+
+ protected boolean hostsEqual(URL url1, URL url2) {
+ URLStreamHandler handler = factory.findAuthorizedURLStreamHandler(protocol);
+ if (handler != null) {
+ try {
+ return ((Boolean) hostsEqualMethod.invoke(handler, new Object[] {url1, url2})).booleanValue();
+ } catch (InvocationTargetException e) {
+ throw (RuntimeException) e.getTargetException();
+ } catch (Exception e) {
+ factory.adaptor.getFrameworkLog().log(new FrameworkLogEntry(MultiplexingURLStreamHandler.class.getName(), FrameworkLogEntry.ERROR, 0, "hostsEqual", 0, e, null)); //$NON-NLS-1$
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+ throw new IllegalStateException();
+ }
+
+ protected void parseURL(URL arg0, String arg1, int arg2, int arg3) {
+ URLStreamHandler handler = factory.findAuthorizedURLStreamHandler(protocol);
+ if (handler != null) {
+ try {
+ // set the real handler for the URL
+ handlerField.set(arg0, handler);
+ parseURLMethod.invoke(handler, new Object[] {arg0, arg1, new Integer(arg2), new Integer(arg3)});
+ return;
+ } catch (InvocationTargetException e) {
+ throw (RuntimeException) e.getTargetException();
+ } catch (Exception e) {
+ factory.adaptor.getFrameworkLog().log(new FrameworkLogEntry(MultiplexingURLStreamHandler.class.getName(), FrameworkLogEntry.ERROR, 0, "parseURL", 0, e, null)); //$NON-NLS-1$
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+ throw new IllegalStateException();
+ }
+
+ protected boolean sameFile(URL url1, URL url2) {
+ URLStreamHandler handler = factory.findAuthorizedURLStreamHandler(protocol);
+ if (handler != null) {
+ try {
+ return ((Boolean) sameFileMethod.invoke(handler, new Object[] {url1, url2})).booleanValue();
+ } catch (InvocationTargetException e) {
+ throw (RuntimeException) e.getTargetException();
+ } catch (Exception e) {
+ factory.adaptor.getFrameworkLog().log(new FrameworkLogEntry(MultiplexingURLStreamHandler.class.getName(), FrameworkLogEntry.ERROR, 0, "sameFile", 0, e, null)); //$NON-NLS-1$
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+ throw new IllegalStateException();
+ }
+
+ protected void setURL(URL arg0, String arg1, String arg2, int arg3, String arg4, String arg5, String arg6, String arg7, String arg8) {
+ URLStreamHandler handler = factory.findAuthorizedURLStreamHandler(protocol);
+ if (handler != null) {
+ try {
+ // set the real handler for the URL
+ handlerField.set(arg0, handler);
+ setURLMethod.invoke(handler, new Object[] {arg0, arg1, arg2, new Integer(arg3), arg4, arg5, arg6, arg7, arg8});
+ return;
+ } catch (InvocationTargetException e) {
+ throw (RuntimeException) e.getTargetException();
+ } catch (Exception e) {
+ factory.adaptor.getFrameworkLog().log(new FrameworkLogEntry(MultiplexingURLStreamHandler.class.getName(), FrameworkLogEntry.ERROR, 0, "setURL", 0, e, null)); //$NON-NLS-1$
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+ throw new IllegalStateException();
+ }
+
+ protected String toExternalForm(URL url) {
+ URLStreamHandler handler = factory.findAuthorizedURLStreamHandler(protocol);
+ if (handler != null) {
+ try {
+ return (String) toExternalFormMethod.invoke(handler, new Object[] {url});
+ } catch (InvocationTargetException e) {
+ throw (RuntimeException) e.getTargetException();
+ } catch (Exception e) {
+ factory.adaptor.getFrameworkLog().log(new FrameworkLogEntry(MultiplexingURLStreamHandler.class.getName(), FrameworkLogEntry.ERROR, 0, "toExternalForm", 0, e, null)); //$NON-NLS-1$
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+ throw new IllegalStateException();
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/NullURLStreamHandlerService.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/NullURLStreamHandlerService.java
new file mode 100644
index 000000000..4cca4f943
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/NullURLStreamHandlerService.java
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.internal.protocol;
+
+import java.io.IOException;
+import java.net.*;
+import org.osgi.service.url.URLStreamHandlerService;
+import org.osgi.service.url.URLStreamHandlerSetter;
+
+/**
+ * The NullURLStreamService is created when a registered URLStreamHandler service
+ * with an associated URLStreamHandlerProxy becomes unregistered. The associated
+ * URLStreamHandlerProxy must still handle all future requests for the now unregistered
+ * scheme (the JVM caches URLStreamHandlers making up impossible to "unregister" them).
+ * When requests come in for an unregistered URLStreamHandlerService, the
+ * NullURLStreamHandlerService is used in it's place.
+ */
+
+public class NullURLStreamHandlerService implements URLStreamHandlerService {
+
+ public URLConnection openConnection(URL u) throws IOException {
+ throw new MalformedURLException();
+ }
+
+ public boolean equals(URL url1, URL url2) {
+ throw new IllegalStateException();
+ }
+
+ public int getDefaultPort() {
+ throw new IllegalStateException();
+ }
+
+ public InetAddress getHostAddress(URL url) {
+ throw new IllegalStateException();
+ }
+
+ public int hashCode(URL url) {
+ throw new IllegalStateException();
+ }
+
+ public boolean hostsEqual(URL url1, URL url2) {
+ throw new IllegalStateException();
+ }
+
+ public boolean sameFile(URL url1, URL url2) {
+ throw new IllegalStateException();
+ }
+
+ public void setURL(URL u, String protocol, String host, int port, String authority, String userInfo, String file, String query, String ref) {
+ throw new IllegalStateException();
+ }
+
+ public void setURL(URL u, String protocol, String host, int port, String file, String ref) {
+ throw new IllegalStateException();
+ }
+
+ public String toExternalForm(URL url) {
+ throw new IllegalStateException();
+ }
+
+ public void parseURL(URLStreamHandlerSetter realHandler, URL u, String spec, int start, int limit) {
+ throw new IllegalStateException();
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/ProtocolActivator.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/ProtocolActivator.java
new file mode 100644
index 000000000..39043c01e
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/ProtocolActivator.java
@@ -0,0 +1,22 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.internal.protocol;
+
+import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor;
+import org.osgi.framework.BundleContext;
+
+//TODO this is non-API, and has no internal clients - do we really need it? at least explain why
+public interface ProtocolActivator {
+
+ public void start(BundleContext context, FrameworkAdaptor adaptor);
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/StreamHandlerFactory.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/StreamHandlerFactory.java
new file mode 100644
index 000000000..3e38687f7
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/StreamHandlerFactory.java
@@ -0,0 +1,223 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.internal.protocol;
+
+import java.lang.reflect.Method;
+import java.net.*;
+import java.security.AccessController;
+import java.util.*;
+import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor;
+import org.eclipse.osgi.framework.internal.core.Constants;
+import org.eclipse.osgi.framework.internal.core.Msg;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+import org.eclipse.osgi.framework.util.SecureAction;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.url.URLConstants;
+import org.osgi.service.url.URLStreamHandlerService;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * This class contains the URL stream handler factory for the OSGi framework.
+ */
+public class StreamHandlerFactory extends MultiplexingFactory implements URLStreamHandlerFactory {
+ static final SecureAction secureAction = AccessController.doPrivileged(SecureAction.createSecureAction());
+
+ private ServiceTracker<URLStreamHandlerService, URLStreamHandlerService> handlerTracker;
+
+ protected static final String URLSTREAMHANDLERCLASS = "org.osgi.service.url.URLStreamHandlerService"; //$NON-NLS-1$
+ protected static final String PROTOCOL_HANDLER_PKGS = "java.protocol.handler.pkgs"; //$NON-NLS-1$
+ protected static final String INTERNAL_PROTOCOL_HANDLER_PKG = "org.eclipse.osgi.framework.internal.protocol"; //$NON-NLS-1$
+
+ private static final List<Class<?>> ignoredClasses = Arrays.asList(new Class<?>[] {MultiplexingURLStreamHandler.class, StreamHandlerFactory.class, URL.class});
+ private static final boolean useNetProxy;
+ static {
+ Class<?> clazz = null;
+ try {
+ clazz = Class.forName("java.net.Proxy"); //$NON-NLS-1$
+ } catch (ClassNotFoundException e) {
+ // expected on JRE < 1.5
+ }
+ useNetProxy = clazz != null;
+ }
+ private Map<String, URLStreamHandler> proxies;
+ private URLStreamHandlerFactory parentFactory;
+ private ThreadLocal<List<String>> creatingProtocols = new ThreadLocal<List<String>>();
+
+ /**
+ * Create the factory.
+ *
+ * @param context BundleContext for the system bundle
+ */
+ public StreamHandlerFactory(BundleContext context, FrameworkAdaptor adaptor) {
+ super(context, adaptor);
+
+ proxies = new Hashtable<String, URLStreamHandler>(15);
+ handlerTracker = new ServiceTracker<URLStreamHandlerService, URLStreamHandlerService>(context, URLSTREAMHANDLERCLASS, null);
+ handlerTracker.open();
+ }
+
+ private Class<?> getBuiltIn(String protocol, String builtInHandlers, boolean fromFramework) {
+ if (builtInHandlers == null)
+ return null;
+ Class<?> clazz;
+ StringTokenizer tok = new StringTokenizer(builtInHandlers, "|"); //$NON-NLS-1$
+ while (tok.hasMoreElements()) {
+ StringBuffer name = new StringBuffer();
+ name.append(tok.nextToken());
+ name.append("."); //$NON-NLS-1$
+ name.append(protocol);
+ name.append(".Handler"); //$NON-NLS-1$
+ try {
+ if (fromFramework)
+ clazz = secureAction.forName(name.toString());
+ else
+ clazz = secureAction.loadSystemClass(name.toString());
+ if (clazz != null)
+ return clazz; //this class exists, it is a built in handler
+ } catch (ClassNotFoundException ex) {
+ // keep looking
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Creates a new URLStreamHandler instance for the specified
+ * protocol.
+ *
+ * @param protocol The desired protocol
+ * @return a URLStreamHandler for the specific protocol.
+ */
+ public URLStreamHandler createURLStreamHandler(String protocol) {
+ // Check if we are recursing
+ if (isRecursive(protocol))
+ return null;
+ try {
+ //first check for built in handlers
+ String builtInHandlers = secureAction.getProperty(PROTOCOL_HANDLER_PKGS);
+ Class<?> clazz = getBuiltIn(protocol, builtInHandlers, false);
+ if (clazz != null)
+ return null; // let the VM handle it
+ URLStreamHandler result = null;
+ if (isMultiplexing()) {
+ if (findAuthorizedURLStreamHandler(protocol) != null)
+ result = new MultiplexingURLStreamHandler(protocol, this);
+ } else {
+ result = createInternalURLStreamHandler(protocol);
+ }
+ // if parent is present do parent lookup
+ if (result == null && parentFactory != null)
+ result = parentFactory.createURLStreamHandler(protocol);
+ return result; //result may be null; let the VM handle it (consider sun.net.protocol.www.*)
+ } catch (Throwable t) {
+ adaptor.getFrameworkLog().log(new FrameworkLogEntry(StreamHandlerFactory.class.getName(), FrameworkLogEntry.ERROR, 0, "Unexpected error in factory.", 0, t, null)); //$NON-NLS-1$
+ return null;
+ } finally {
+ releaseRecursive(protocol);
+ }
+ }
+
+ private boolean isRecursive(String protocol) {
+ List<String> protocols = creatingProtocols.get();
+ if (protocols == null) {
+ protocols = new ArrayList<String>(1);
+ creatingProtocols.set(protocols);
+ }
+ if (protocols.contains(protocol))
+ return true;
+ protocols.add(protocol);
+ return false;
+ }
+
+ private void releaseRecursive(String protocol) {
+ List<String> protocols = creatingProtocols.get();
+ protocols.remove(protocol);
+ }
+
+ public URLStreamHandler createInternalURLStreamHandler(String protocol) {
+ //internal protocol handlers
+ String internalHandlerPkgs = secureAction.getProperty(Constants.INTERNAL_HANDLER_PKGS);
+ internalHandlerPkgs = internalHandlerPkgs == null ? INTERNAL_PROTOCOL_HANDLER_PKG : internalHandlerPkgs + '|' + INTERNAL_PROTOCOL_HANDLER_PKG;
+ Class<?> clazz = getBuiltIn(protocol, internalHandlerPkgs, true);
+
+ if (clazz == null) {
+ //Now we check the service registry
+ //first check to see if the handler is in the cache
+ URLStreamHandlerProxy handler = (URLStreamHandlerProxy) proxies.get(protocol);
+ if (handler != null)
+ return (handler);
+ //look through the service registry for a URLStramHandler registered for this protocol
+ ServiceReference<URLStreamHandlerService>[] serviceReferences = handlerTracker.getServiceReferences();
+ if (serviceReferences == null)
+ return null;
+ for (int i = 0; i < serviceReferences.length; i++) {
+ Object prop = serviceReferences[i].getProperty(URLConstants.URL_HANDLER_PROTOCOL);
+ if (prop instanceof String)
+ prop = new String[] {(String) prop}; // TODO should this be a warning?
+ if (!(prop instanceof String[])) {
+ String message = NLS.bind(Msg.URL_HANDLER_INCORRECT_TYPE, new Object[] {URLConstants.URL_HANDLER_PROTOCOL, URLSTREAMHANDLERCLASS, serviceReferences[i].getBundle()});
+ adaptor.getFrameworkLog().log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.WARNING, 0, message, 0, null, null));
+ continue;
+ }
+ String[] protocols = (String[]) prop;
+ for (int j = 0; j < protocols.length; j++)
+ if (protocols[j].equals(protocol)) {
+ handler = useNetProxy ? new URLStreamHandlerFactoryProxyFor15(protocol, serviceReferences[i], context) : new URLStreamHandlerProxy(protocol, serviceReferences[i], context);
+ proxies.put(protocol, handler);
+ return (handler);
+ }
+ }
+ return null;
+ }
+
+ // must be a built-in handler
+ try {
+ URLStreamHandler handler = (URLStreamHandler) clazz.newInstance();
+
+ if (handler instanceof ProtocolActivator) {
+ ((ProtocolActivator) handler).start(context, adaptor);
+ }
+
+ return handler;
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ protected URLStreamHandler findAuthorizedURLStreamHandler(String protocol) {
+ Object factory = findAuthorizedFactory(ignoredClasses);
+ if (factory == null)
+ return null;
+
+ if (factory == this)
+ return createInternalURLStreamHandler(protocol);
+
+ try {
+ Method createInternalURLStreamHandlerMethod = factory.getClass().getMethod("createInternalURLStreamHandler", new Class[] {String.class}); //$NON-NLS-1$
+ return (URLStreamHandler) createInternalURLStreamHandlerMethod.invoke(factory, new Object[] {protocol});
+ } catch (Exception e) {
+ adaptor.getFrameworkLog().log(new FrameworkLogEntry(StreamHandlerFactory.class.getName(), FrameworkLogEntry.ERROR, 0, "findAuthorizedURLStreamHandler-loop", 0, e, null)); //$NON-NLS-1$
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+
+ public Object getParentFactory() {
+ return parentFactory;
+ }
+
+ public void setParentFactory(Object parentFactory) {
+ if (this.parentFactory == null) // only allow it to be set once
+ this.parentFactory = (URLStreamHandlerFactory) parentFactory;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/URLStreamHandlerFactoryProxyFor15.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/URLStreamHandlerFactoryProxyFor15.java
new file mode 100644
index 000000000..ddfaf80f3
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/URLStreamHandlerFactoryProxyFor15.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.internal.protocol;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.*;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.url.URLStreamHandlerService;
+
+public class URLStreamHandlerFactoryProxyFor15 extends URLStreamHandlerProxy {
+
+ public URLStreamHandlerFactoryProxyFor15(String protocol, ServiceReference<URLStreamHandlerService> reference, BundleContext context) {
+ super(protocol, reference, context);
+ }
+
+ protected URLConnection openConnection(URL u, Proxy p) throws IOException {
+ try {
+ Method openConn = realHandlerService.getClass().getMethod("openConnection", new Class[] {URL.class, Proxy.class}); //$NON-NLS-1$
+ return (URLConnection) openConn.invoke(realHandlerService, new Object[] {u, p});
+ } catch (InvocationTargetException e) {
+ if (e.getTargetException() instanceof IOException)
+ throw (IOException) e.getTargetException();
+ throw (RuntimeException) e.getTargetException();
+ } catch (Exception e) {
+ // expected on JRE < 1.5
+ throw (UnsupportedOperationException) new UnsupportedOperationException().initCause(e);
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/URLStreamHandlerProxy.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/URLStreamHandlerProxy.java
new file mode 100644
index 000000000..cb0dc57ae
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/URLStreamHandlerProxy.java
@@ -0,0 +1,218 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.internal.protocol;
+
+import java.io.IOException;
+import java.net.*;
+import org.osgi.framework.*;
+import org.osgi.service.url.URLConstants;
+import org.osgi.service.url.URLStreamHandlerService;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+
+/**
+ * The URLStreamHandlerProxy is a URLStreamHandler that acts as a proxy for registered
+ * URLStreamHandlerServices. When a URLStreamHandler is requested from the URLStreamHandlerFactory
+ * and it exists in the service registry, a URLStreamHandlerProxy is created which will pass all the
+ * requests from the requestor to the real URLStreamHandlerService. We can't return the real
+ * URLStreamHandlerService from the URLStreamHandlerFactory because the JVM caches URLStreamHandlers
+ * and therefore would not support a dynamic environment of URLStreamHandlerServices being registered
+ * and unregistered.
+ */
+
+public class URLStreamHandlerProxy extends URLStreamHandler implements ServiceTrackerCustomizer<URLStreamHandlerService, ServiceReference<URLStreamHandlerService>> {
+ // TODO lots of type-based names
+ protected URLStreamHandlerService realHandlerService;
+
+ protected URLStreamHandlerSetter urlSetter;
+
+ protected ServiceTracker<URLStreamHandlerService, ServiceReference<URLStreamHandlerService>> urlStreamHandlerServiceTracker;
+
+ protected BundleContext context;
+ protected ServiceReference<URLStreamHandlerService> urlStreamServiceReference;
+
+ protected String protocol;
+
+ protected int ranking = Integer.MIN_VALUE;
+
+ public URLStreamHandlerProxy(String protocol, ServiceReference<URLStreamHandlerService> reference, BundleContext context) {
+ this.context = context;
+ this.protocol = protocol;
+
+ urlSetter = new URLStreamHandlerSetter(this);
+
+ //set the handler and ranking
+ setNewHandler(reference, getRank(reference));
+
+ urlStreamHandlerServiceTracker = new ServiceTracker<URLStreamHandlerService, ServiceReference<URLStreamHandlerService>>(context, StreamHandlerFactory.URLSTREAMHANDLERCLASS, this);
+ StreamHandlerFactory.secureAction.open(urlStreamHandlerServiceTracker);
+ }
+
+ private void setNewHandler(ServiceReference<URLStreamHandlerService> reference, int rank) {
+ if (urlStreamServiceReference != null)
+ context.ungetService(urlStreamServiceReference);
+
+ urlStreamServiceReference = reference;
+ ranking = rank;
+
+ if (reference == null)
+ realHandlerService = new NullURLStreamHandlerService();
+ else
+ realHandlerService = StreamHandlerFactory.secureAction.getService(reference, context);
+ }
+
+ /**
+ * @see java.net.URLStreamHandler#equals(URL, URL)
+ */
+ protected boolean equals(URL url1, URL url2) {
+ return realHandlerService.equals(url1, url2);
+ }
+
+ /**
+ * @see java.net.URLStreamHandler#getDefaultPort()
+ */
+ protected int getDefaultPort() {
+ return realHandlerService.getDefaultPort();
+ }
+
+ /**
+ * @see java.net.URLStreamHandler#getHostAddress(URL)
+ */
+ protected InetAddress getHostAddress(URL url) {
+ return realHandlerService.getHostAddress(url);
+ }
+
+ /**
+ * @see java.net.URLStreamHandler#hashCode(URL)
+ */
+ protected int hashCode(URL url) {
+ return realHandlerService.hashCode(url);
+ }
+
+ /**
+ * @see java.net.URLStreamHandler#hostsEqual(URL, URL)
+ */
+ protected boolean hostsEqual(URL url1, URL url2) {
+ return realHandlerService.hostsEqual(url1, url2);
+ }
+
+ /**
+ * @see java.net.URLStreamHandler#openConnection(URL)
+ */
+ protected URLConnection openConnection(URL url) throws IOException {
+ return realHandlerService.openConnection(url);
+ }
+
+ /**
+ * @see java.net.URLStreamHandler#parseURL(URL, String, int, int)
+ */
+ protected void parseURL(URL url, String str, int start, int end) {
+ realHandlerService.parseURL(urlSetter, url, str, start, end);
+ }
+
+ /**
+ * @see java.net.URLStreamHandler#sameFile(URL, URL)
+ */
+ protected boolean sameFile(URL url1, URL url2) {
+ return realHandlerService.sameFile(url1, url2);
+ }
+
+ /**
+ * @see java.net.URLStreamHandler#toExternalForm(URL)
+ */
+ protected String toExternalForm(URL url) {
+ return realHandlerService.toExternalForm(url);
+ }
+
+ /**
+ * @see java.net.URLStreamHandler#setURL(URL, String, String, int, String, String, String, String, String)
+ */
+ public void setURL(URL u, String protocol, String host, int port, String authority, String userInfo, String file, String query, String ref) {
+ super.setURL(u, protocol, host, port, authority, userInfo, file, query, ref);
+ }
+
+ @SuppressWarnings("deprecation")
+ public void setURL(URL url, String protocol, String host, int port, String file, String ref) {
+
+ //using non-deprecated URLStreamHandler.setURL method.
+ //setURL(URL u, String protocol, String host, int port, String authority, String userInfo, String file, String query, String ref)
+ super.setURL(url, protocol, host, port, null, null, file, null, ref);
+ }
+
+ /**
+ * @see org.osgi.util.tracker.ServiceTrackerCustomizer#addingService(ServiceReference)
+ */
+ public ServiceReference<URLStreamHandlerService> addingService(ServiceReference<URLStreamHandlerService> reference) {
+ //check to see if our protocol is being registered by another service
+ Object prop = reference.getProperty(URLConstants.URL_HANDLER_PROTOCOL);
+ if (!(prop instanceof String[]))
+ return null;
+ String[] protocols = (String[]) prop;
+ for (int i = 0; i < protocols.length; i++) {
+ if (protocols[i].equals(protocol)) {
+ //If our protocol is registered by another service, check the service ranking and switch URLStreamHandlers if nessecary.
+ int newServiceRanking = getRank(reference);
+ if (newServiceRanking > ranking || urlStreamServiceReference == null)
+ setNewHandler(reference, newServiceRanking);
+ return reference;
+ }
+ }
+
+ //we don't want to continue hearing events about a URLStreamHandlerService not registered under our protocol
+ return null;
+ }
+
+ /**
+ * @see org.osgi.util.tracker.ServiceTrackerCustomizer#modifiedService(ServiceReference, Object)
+ */
+ // check to see if the ranking has changed. If so, re-select a new URLHandler
+ public void modifiedService(ServiceReference<URLStreamHandlerService> reference, ServiceReference<URLStreamHandlerService> service) {
+ int newRank = getRank(reference);
+ if (reference == urlStreamServiceReference) {
+ if (newRank < ranking) {
+ // The URLHandler we are currently using has dropped it's ranking below a URLHandler registered
+ // for the same protocol. We need to swap out URLHandlers.
+ // this should get us the highest ranked service, if available
+ ServiceReference<URLStreamHandlerService> newReference = urlStreamHandlerServiceTracker.getServiceReference();
+ if (newReference != urlStreamServiceReference && newReference != null) {
+ setNewHandler(newReference, ((Integer) newReference.getProperty(Constants.SERVICE_RANKING)).intValue());
+ }
+ }
+ } else if (newRank > ranking) {
+ // the service changed is another URLHandler that we are not currently using
+ // If it's ranking is higher, we must swap it in.
+ setNewHandler(reference, newRank);
+ }
+ }
+
+ /**
+ * @see org.osgi.util.tracker.ServiceTrackerCustomizer#removedService(ServiceReference, Object)
+ */
+ public void removedService(ServiceReference<URLStreamHandlerService> reference, ServiceReference<URLStreamHandlerService> service) {
+ // check to see if our URLStreamHandler was unregistered.
+ if (reference != urlStreamServiceReference)
+ return;
+ // If so, look for a lower ranking URLHandler
+ // this should get us the highest ranking service left, if available
+ ServiceReference<URLStreamHandlerService> newReference = urlStreamHandlerServiceTracker.getServiceReference();
+ // if newReference == null then we will use the NullURLStreamHandlerService here
+ setNewHandler(newReference, getRank(newReference));
+ }
+
+ private int getRank(ServiceReference<?> reference) {
+ if (reference == null)
+ return Integer.MIN_VALUE;
+ Object property = reference.getProperty(Constants.SERVICE_RANKING);
+ return (property instanceof Integer) ? ((Integer) property).intValue() : 0;
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/URLStreamHandlerSetter.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/URLStreamHandlerSetter.java
new file mode 100644
index 000000000..c576b5a2b
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/URLStreamHandlerSetter.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.internal.protocol;
+
+import java.net.URL;
+
+public class URLStreamHandlerSetter implements org.osgi.service.url.URLStreamHandlerSetter {
+
+ protected URLStreamHandlerProxy handlerProxy;
+
+ public URLStreamHandlerSetter(URLStreamHandlerProxy handler) {
+ this.handlerProxy = handler;
+ }
+
+ /**
+ * @see org.osgi.service.url.URLStreamHandlerSetter#setURL(URL, String, String, int, String, String)
+ * @deprecated
+ */
+ public void setURL(URL url, String protocol, String host, int port, String file, String ref) {
+ handlerProxy.setURL(url, protocol, host, port, file, ref);
+ }
+
+ /**
+ * @see org.osgi.service.url.URLStreamHandlerSetter#setURL(URL, String, String, int, String, String, String, String, String)
+ */
+ public void setURL(URL url, String protocol, String host, int port, String authority, String userInfo, String path, String query, String ref) {
+ handlerProxy.setURL(url, protocol, host, port, authority, userInfo, path, query, ref);
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/bundleentry/Handler.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/bundleentry/Handler.java
new file mode 100644
index 000000000..5e39fdcf0
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/bundleentry/Handler.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2007 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.internal.protocol.bundleentry;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.URL;
+import org.eclipse.osgi.baseadaptor.BaseAdaptor;
+import org.eclipse.osgi.baseadaptor.BaseData;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
+import org.eclipse.osgi.framework.internal.core.AbstractBundle;
+import org.eclipse.osgi.framework.internal.core.BundleResourceHandler;
+
+/**
+ * URLStreamHandler the bundleentry protocol.
+ */
+
+public class Handler extends BundleResourceHandler {
+
+ /**
+ * Constructor for a bundle protocol resource URLStreamHandler.
+ */
+ public Handler() {
+ super();
+ }
+
+ public Handler(BundleEntry bundleEntry, BaseAdaptor adaptor) {
+ super(bundleEntry, adaptor);
+ }
+
+ protected BundleEntry findBundleEntry(URL url, AbstractBundle bundle) throws IOException {
+ BaseData bundleData = (BaseData) bundle.getBundleData();
+ BundleEntry entry = bundleData.getBundleFile().getEntry(url.getPath());
+ if (entry == null)
+ throw new FileNotFoundException(url.getPath());
+ return entry;
+
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/bundleresource/Handler.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/bundleresource/Handler.java
new file mode 100644
index 000000000..4dd5c7c2e
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/bundleresource/Handler.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2009 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.internal.protocol.bundleresource;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.URL;
+import org.eclipse.osgi.baseadaptor.BaseAdaptor;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
+import org.eclipse.osgi.baseadaptor.loader.BaseClassLoader;
+import org.eclipse.osgi.baseadaptor.loader.ClasspathManager;
+import org.eclipse.osgi.framework.internal.core.AbstractBundle;
+import org.eclipse.osgi.framework.internal.core.BundleResourceHandler;
+
+/**
+ * URLStreamHandler the bundleresource protocol.
+ */
+
+public class Handler extends BundleResourceHandler {
+
+ /**
+ * Constructor for a bundle protocol resource URLStreamHandler.
+ */
+ public Handler() {
+ super();
+ }
+
+ public Handler(BundleEntry bundleEntry, BaseAdaptor adaptor) {
+ super(bundleEntry, adaptor);
+ }
+
+ protected BundleEntry findBundleEntry(URL url, AbstractBundle bundle) throws IOException {
+ BaseClassLoader classloader = getBundleClassLoader(bundle);
+ if (classloader == null)
+ throw new FileNotFoundException(url.getPath());
+ ClasspathManager cpManager = classloader.getClasspathManager();
+ BundleEntry entry = cpManager.findLocalEntry(url.getPath(), url.getPort());
+ if (entry == null)
+ // this isn't strictly needed but is kept to maintain compatibility
+ entry = cpManager.findLocalEntry(url.getPath());
+ if (entry == null)
+ throw new FileNotFoundException(url.getPath());
+ return entry;
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/reference/Handler.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/reference/Handler.java
new file mode 100644
index 000000000..d26175e55
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/reference/Handler.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.internal.protocol.reference;
+
+import java.io.IOException;
+import java.net.*;
+
+/**
+ * URLStreamHandler for reference protocol. A reference URL is used to hold a
+ * reference to a local file URL. A reference URL allows bundles to be installed
+ * by reference. This means the content of the bundle will not be copied. Instead
+ * the content of the bundle will be loaded from the reference location specified
+ * by the reference URL. The Framework only supports reference URLs that refer
+ * to a local file URL. For example: <p>
+ * <pre>
+ * reference:file:/eclipse/plugins/org.eclipse.myplugin_1.0.0/
+ * reference:file:/eclispe/plugins/org.eclipse.mybundle_1.0.0.jar
+ * </pre>
+ */
+public class Handler extends URLStreamHandler {
+
+ /**
+ * @throws IOException
+ */
+ protected URLConnection openConnection(URL url) throws IOException {
+ return new ReferenceURLConnection(url);
+ }
+
+ protected void parseURL(URL url, String str, int start, int end) {
+ if (end < start) {
+ return;
+ }
+ String reference = (start < end) ? str.substring(start, end) : url.getPath();
+
+ setURL(url, url.getProtocol(), null, -1, null, null, reference, null, null);
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/reference/ReferenceURLConnection.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/reference/ReferenceURLConnection.java
new file mode 100644
index 000000000..f58a4b302
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/protocol/reference/ReferenceURLConnection.java
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.internal.protocol.reference;
+
+import java.io.*;
+import java.net.URL;
+import java.net.URLConnection;
+import org.eclipse.osgi.framework.adaptor.FilePath;
+import org.eclipse.osgi.framework.internal.core.FrameworkProperties;
+import org.eclipse.osgi.framework.internal.core.ReferenceInputStream;
+
+/**
+ * URLConnection for the reference protocol.
+ */
+
+public class ReferenceURLConnection extends URLConnection {
+ protected URL reference;
+
+ protected ReferenceURLConnection(URL url) {
+ super(url);
+ }
+
+ @SuppressWarnings("deprecation")
+ public synchronized void connect() throws IOException {
+ if (!connected) {
+ // TODO assumes that reference URLs are always based on file: URLs.
+ // There are not solid usecases to the contrary. Yet.
+ // Construct the ref URL carefully so as to preserve UNC paths etc.
+ String path = url.getPath().substring(5);
+ File file = new File(path);
+
+ URL ref;
+ if (!file.isAbsolute()) {
+ String installPath = getInstallPath();
+ if (installPath != null)
+ file = makeAbsolute(installPath, file);
+ }
+
+ // Pre-check if file exists, if not, and it contains escape characters,
+ // try decoding the absolute path generated by makeAbsolute
+ if (!file.exists() && path.indexOf('%') >= 0) {
+ String decodePath = FrameworkProperties.decode(file.getAbsolutePath());
+ File f = new File(decodePath);
+ if (f.exists())
+ file = f;
+ }
+
+ ref = file.toURL();
+ checkRead(file);
+
+ reference = ref;
+ }
+ }
+
+ private void checkRead(File file) throws IOException {
+ if (!file.exists())
+ throw new FileNotFoundException(file.toString());
+ if (file.isFile()) {
+ // Try to open the file to ensure that this is possible: see bug 260217
+ // If access is denied, a FileNotFoundException with (access denied) message is thrown
+ // Here file.canRead() cannot be used, because on Windows it does not
+ // return correct values - bug 6203387 in Sun's bug database
+ InputStream is = new FileInputStream(file);
+ is.close();
+ } else if (file.isDirectory()) {
+ // There is no straightforward way to check if a directory
+ // has read permissions - same issues for File.canRead() as above;
+ // try to list the files in the directory
+ File[] files = file.listFiles();
+ // File.listFiles() returns null if the directory does not exist
+ // (which is not the current case, because we check that it exists and is directory),
+ // or if an IO error occurred during the listing of the files, including if the
+ // access is denied
+ if (files == null)
+ throw new FileNotFoundException(file.toString() + " (probably access denied)"); //$NON-NLS-1$
+ } else {
+ // TODO not sure if we can get here.
+ }
+ }
+
+ public boolean getDoInput() {
+ return true;
+ }
+
+ public boolean getDoOutput() {
+ return false;
+ }
+
+ public InputStream getInputStream() throws IOException {
+ if (!connected) {
+ connect();
+ }
+
+ return new ReferenceInputStream(reference);
+ }
+
+ private String getInstallPath() {
+ String installURL = FrameworkProperties.getProperty("osgi.install.area"); //$NON-NLS-1$
+ if (installURL == null)
+ return null;
+ if (!installURL.startsWith("file:")) //$NON-NLS-1$
+ return null;
+ // this is the safest way to create a File object off a file: URL
+ return installURL.substring(5);
+ }
+
+ private static File makeAbsolute(String base, File relative) {
+ if (relative.isAbsolute())
+ return relative;
+ return new File(new FilePath(base + relative.getPath()).toString());
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/util/Headers.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/util/Headers.java
new file mode 100644
index 000000000..3268b3efc
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/util/Headers.java
@@ -0,0 +1,313 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.framework.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.*;
+import org.eclipse.osgi.framework.internal.core.Msg;
+import org.eclipse.osgi.util.ManifestElement;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.BundleException;
+
+/**
+ * Headers classes. This class implements a Dictionary that has
+ * the following behavior:
+ * <ul>
+ * <li>put and remove clear throw UnsupportedOperationException.
+ * The Dictionary is thus read-only to others.
+ * <li>The String keys in the Dictionary are case-preserved,
+ * but the get operation is case-insensitive.
+ * </ul>
+ * @since 3.1
+ */
+public class Headers<K, V> extends Dictionary<K, V> implements Map<K, V> {
+ private boolean readOnly = false;
+ private K[] headers;
+ private V[] values;
+ private int size = 0;
+
+ /**
+ * Create an empty Headers dictionary.
+ *
+ * @param initialCapacity The initial capacity of this Headers object.
+ */
+ public Headers(int initialCapacity) {
+ super();
+ @SuppressWarnings("unchecked")
+ K[] k = (K[]) new Object[initialCapacity];
+ headers = k;
+ @SuppressWarnings("unchecked")
+ V[] v = (V[]) new Object[initialCapacity];
+ values = v;
+ }
+
+ /**
+ * Create a Headers dictionary from a Dictionary.
+ *
+ * @param values The initial dictionary for this Headers object.
+ * @exception IllegalArgumentException If a case-variant of the key is
+ * in the dictionary parameter.
+ */
+ public Headers(Dictionary<? extends K, ? extends V> values) {
+ this(values.size());
+ /* initialize the headers and values */
+ Enumeration<? extends K> keys = values.keys();
+ while (keys.hasMoreElements()) {
+ K key = keys.nextElement();
+ set(key, values.get(key));
+ }
+ }
+
+ /**
+ * Case-preserved keys.
+ */
+ public synchronized Enumeration<K> keys() {
+ return new ArrayEnumeration<K>(headers, size);
+ }
+
+ /**
+ * Values.
+ */
+ public synchronized Enumeration<V> elements() {
+ return new ArrayEnumeration<V>(values, size);
+ }
+
+ private int getIndex(Object key) {
+ boolean stringKey = key instanceof String;
+ for (int i = 0; i < size; i++) {
+ if (stringKey && (headers[i] instanceof String)) {
+ if (((String) headers[i]).equalsIgnoreCase((String) key))
+ return i;
+ } else {
+ if (headers[i].equals(key))
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private V remove(int remove) {
+ V removed = values[remove];
+ for (int i = remove; i < size; i++) {
+ if (i == headers.length - 1) {
+ headers[i] = null;
+ values[i] = null;
+ } else {
+ headers[i] = headers[i + 1];
+ values[i] = values[i + 1];
+ }
+ }
+ if (remove < size)
+ size--;
+ return removed;
+ }
+
+ private void add(K header, V value) {
+ if (size == headers.length) {
+ // grow the arrays
+ @SuppressWarnings("unchecked")
+ K[] nh = (K[]) new Object[headers.length + 10];
+ K[] newHeaders = nh;
+ @SuppressWarnings("unchecked")
+ V[] nv = (V[]) new Object[values.length + 10];
+ V[] newValues = nv;
+ System.arraycopy(headers, 0, newHeaders, 0, headers.length);
+ System.arraycopy(values, 0, newValues, 0, values.length);
+ headers = newHeaders;
+ values = newValues;
+ }
+ headers[size] = header;
+ values[size] = value;
+ size++;
+ }
+
+ /**
+ * Support case-insensitivity for keys.
+ *
+ * @param key name.
+ */
+ public synchronized V get(Object key) {
+ int i = -1;
+ if ((i = getIndex(key)) != -1)
+ return values[i];
+ return null;
+ }
+
+ /**
+ * Set a header value or optionally replace it if it already exists.
+ *
+ * @param key Key name.
+ * @param value Value of the key or null to remove key.
+ * @param replace A value of true will allow a previous
+ * value of the key to be replaced. A value of false
+ * will cause an IllegalArgumentException to be thrown
+ * if a previous value of the key exists.
+ * @return the previous value to which the key was mapped,
+ * or null if the key did not have a previous mapping.
+ *
+ * @exception IllegalArgumentException If a case-variant of the key is
+ * already present.
+ * @since 3.2
+ */
+ public synchronized V set(K key, V value, boolean replace) {
+ if (readOnly)
+ throw new UnsupportedOperationException();
+ if (key instanceof String) {
+ @SuppressWarnings("unchecked")
+ K k = (K) ((String) key).intern();
+ key = k;
+ }
+ int i = getIndex(key);
+ if (value == null) { /* remove */
+ if (i != -1)
+ return remove(i);
+ } else { /* put */
+ if (i != -1) { /* duplicate key */
+ if (!replace)
+ throw new IllegalArgumentException(NLS.bind(Msg.HEADER_DUPLICATE_KEY_EXCEPTION, key));
+ V oldVal = values[i];
+ values[i] = value;
+ return oldVal;
+ }
+ add(key, value);
+ }
+ return null;
+ }
+
+ /**
+ * Set a header value.
+ *
+ * @param key Key name.
+ * @param value Value of the key or null to remove key.
+ * @return the previous value to which the key was mapped,
+ * or null if the key did not have a previous mapping.
+ *
+ * @exception IllegalArgumentException If a case-variant of the key is
+ * already present.
+ */
+ public synchronized V set(K key, V value) {
+ return set(key, value, false);
+ }
+
+ public synchronized void setReadOnly() {
+ readOnly = true;
+ }
+
+ /**
+ * Returns the number of entries (distinct keys) in this dictionary.
+ *
+ * @return the number of keys in this dictionary.
+ */
+ public synchronized int size() {
+ return size;
+ }
+
+ /**
+ * Tests if this dictionary maps no keys to value. The general contract
+ * for the <tt>isEmpty</tt> method is that the result is true if and only
+ * if this dictionary contains no entries.
+ *
+ * @return <code>true</code> if this dictionary maps no keys to values;
+ * <code>false</code> otherwise.
+ */
+ public synchronized boolean isEmpty() {
+ return size == 0;
+ }
+
+ /**
+ * Always throws UnsupportedOperationException.
+ *
+ * @param key header name.
+ * @param value header value.
+ * @throws UnsupportedOperationException
+ */
+ public synchronized V put(K key, V value) {
+ if (readOnly)
+ throw new UnsupportedOperationException();
+ return set(key, value, true);
+ }
+
+ /**
+ * Always throws UnsupportedOperationException.
+ *
+ * @param key header name.
+ * @throws UnsupportedOperationException
+ */
+ public V remove(Object key) {
+ throw new UnsupportedOperationException();
+ }
+
+ public String toString() {
+ return values.toString();
+ }
+
+ public static Headers<String, String> parseManifest(InputStream in) throws BundleException {
+ Headers<String, String> headers = new Headers<String, String>(10);
+ try {
+ ManifestElement.parseBundleManifest(in, headers);
+ } catch (IOException e) {
+ throw new BundleException(Msg.MANIFEST_IOEXCEPTION, BundleException.MANIFEST_ERROR, e);
+ }
+ headers.setReadOnly();
+ return headers;
+ }
+
+ private static class ArrayEnumeration<E> implements Enumeration<E> {
+ private E[] array;
+ int cur = 0;
+
+ public ArrayEnumeration(E[] array, int size) {
+ @SuppressWarnings("unchecked")
+ E[] a = (E[]) new Object[size];
+ this.array = a;
+ System.arraycopy(array, 0, this.array, 0, this.array.length);
+ }
+
+ public boolean hasMoreElements() {
+ return cur < array.length;
+ }
+
+ public E nextElement() {
+ return array[cur++];
+ }
+ }
+
+ public synchronized void clear() {
+ if (readOnly)
+ throw new UnsupportedOperationException();
+ }
+
+ public synchronized boolean containsKey(Object key) {
+ return getIndex(key) >= 0;
+ }
+
+ public boolean containsValue(Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Set<Map.Entry<K, V>> entrySet() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Set<K> keySet() {
+ throw new UnsupportedOperationException();
+ }
+
+ public void putAll(Map<? extends K, ? extends V> c) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Collection<V> values() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/util/KeyedElement.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/util/KeyedElement.java
new file mode 100644
index 000000000..a995e5840
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/util/KeyedElement.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.framework.util;
+
+/**
+ * An element of an <code>KeyedHashSet</code>. A KeyedElement privides the key which is used to hash
+ * the elements in an <code>KeyedHashSet</code>.
+ * @see KeyedHashSet
+ * @since 3.2
+ */
+// This class was moved from /org.eclipse.osgi/core/framework/org/eclipse/osgi/framework/internal/core/KeyedElement.java
+public interface KeyedElement {
+ /**
+ * Returns the hash code of the key
+ * @return the hash code of the key
+ */
+ public int getKeyHashCode();
+
+ /**
+ * Compares this element with a specified element
+ * @param other the element to compare with
+ * @return returns true if the specified element equals this element
+ */
+ public boolean compare(KeyedElement other);
+
+ /**
+ * Returns the key for this element
+ * @return the key for this element
+ */
+ public Object getKey();
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/util/KeyedHashSet.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/util/KeyedHashSet.java
new file mode 100644
index 000000000..0fc3f415f
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/util/KeyedHashSet.java
@@ -0,0 +1,490 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.framework.util;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * A set data structure which only accepts {@link KeyedElement} objects as elements of the set.
+ * Unlike typical set implementations this set requires each element to provide its own key. This helps
+ * reduce the overhead of storing the keys of each individual element<p>
+ * This class in not thread safe, clients must ensure synchronization when modifying an object of this type.
+ * @since 3.2
+ */
+// This class was moved from /org.eclipse.osgi/core/framework/org/eclipse/osgi/framework/internal/core/KeyedHashSet.java
+public class KeyedHashSet {
+ public static final int MINIMUM_SIZE = 7;
+ int elementCount = 0;
+ KeyedElement[] elements;
+ private boolean replace;
+ private int capacity;
+
+ /**
+ * Constructs an KeyedHashSet which allows elements to be replaced and with the minimum initial capacity.
+ */
+ public KeyedHashSet() {
+ this(MINIMUM_SIZE, true);
+ }
+
+ /**
+ * Constructs an KeyedHashSet with the minimum initial capacity.
+ * @param replace true if this set allows elements to be replaced
+ */
+ public KeyedHashSet(boolean replace) {
+ this(MINIMUM_SIZE, replace);
+ }
+
+ /**
+ * Constructs an KeyedHashSet which allows elements to be replaced.
+ * @param capacity the initial capacity of this set
+ */
+ public KeyedHashSet(int capacity) {
+ this(capacity, true);
+ }
+
+ /**
+ * Constructs an KeyedHashSet
+ * @param capacity the initial capacity of this set
+ * @param replace true if this set allows elements to be replaced
+ */
+ public KeyedHashSet(int capacity, boolean replace) {
+ elements = new KeyedElement[Math.max(MINIMUM_SIZE, capacity * 2)];
+ this.replace = replace;
+ this.capacity = capacity;
+ }
+
+ /**
+ * Constructs a new KeyedHashSet and copies to specified KeyedHashSet's contents to the new KeyedHashSet.
+ * @param original the KeyedHashSet to copy
+ */
+ public KeyedHashSet(KeyedHashSet original) {
+ elements = new KeyedElement[original.elements.length];
+ System.arraycopy(original.elements, 0, elements, 0, original.elements.length);
+ elementCount = original.elementCount;
+ replace = original.replace;
+ capacity = original.capacity;
+ }
+
+ /**
+ * Adds an element to this set. If an element with the same key already exists,
+ * replaces it depending on the replace flag.
+ * @return true if the element was added/stored, false otherwise
+ */
+ public boolean add(KeyedElement element) {
+ int hash = hash(element);
+
+ // search for an empty slot at the end of the array
+ for (int i = hash; i < elements.length; i++) {
+ if (elements[i] == null) {
+ elements[i] = element;
+ elementCount++;
+ // grow if necessary
+ if (shouldGrow())
+ expand();
+ return true;
+ }
+ if (elements[i].compare(element)) {
+ if (replace)
+ elements[i] = element;
+ return replace;
+ }
+ }
+
+ // search for an empty slot at the beginning of the array
+ for (int i = 0; i < hash - 1; i++) {
+ if (elements[i] == null) {
+ elements[i] = element;
+ elementCount++;
+ // grow if necessary
+ if (shouldGrow())
+ expand();
+ return true;
+ }
+ if (elements[i].compare(element)) {
+ if (replace)
+ elements[i] = element;
+ return replace;
+ }
+ }
+
+ // if we didn't find a free slot, then try again with the expanded set
+ expand();
+ return add(element);
+ }
+
+ /**
+ * Adds the specified list of elements to this set. Some elements may not
+ * get added if the replace flag is set.
+ * @param toAdd the list of elements to add to this set.
+ */
+ public void addAll(KeyedElement[] toAdd) {
+ for (int i = 0; i < toAdd.length; i++)
+ add(toAdd[i]);
+ }
+
+ /**
+ * Returns true if the specified element exists in this set.
+ * @param element the requested element
+ * @return true if the specified element exists in this set; false otherwise.
+ */
+ public boolean contains(KeyedElement element) {
+ return get(element) != null;
+ }
+
+ /**
+ * Returns true if an element with the specified key exists in this set.
+ * @param key the key of the requested element
+ * @return true if an element with the specified key exists in this set; false otherwise
+ */
+ public boolean containsKey(Object key) {
+ return getByKey(key) != null;
+ }
+
+ /**
+ * Returns all elements that exist in this set
+ * @return all elements that exist in this set
+ */
+ public KeyedElement[] elements() {
+ return (KeyedElement[]) elements(new KeyedElement[elementCount]);
+ }
+
+ /**
+ * Copies all elements that exist in this set into the specified array. No size
+ * checking is done. If the specified array is to small an ArrayIndexOutOfBoundsException
+ * will be thrown.
+ * @param result the array to copy the existing elements into.
+ * @return the specified array.
+ * @throws ArrayIndexOutOfBoundsException if the specified array is to small.
+ */
+ public Object[] elements(Object[] result) {
+ int j = 0;
+ for (int i = 0; i < elements.length; i++) {
+ KeyedElement element = elements[i];
+ if (element != null)
+ result[j++] = element;
+ }
+ return result;
+ }
+
+ /**
+ * The array isn't large enough so double its size and rehash
+ * all its current values.
+ */
+ protected void expand() {
+ KeyedElement[] oldElements = elements;
+ elements = new KeyedElement[elements.length * 2];
+
+ int maxArrayIndex = elements.length - 1;
+ for (int i = 0; i < oldElements.length; i++) {
+ KeyedElement element = oldElements[i];
+ if (element != null) {
+ int hash = hash(element);
+ while (elements[hash] != null) {
+ hash++;
+ if (hash > maxArrayIndex)
+ hash = 0;
+ }
+ elements[hash] = element;
+ }
+ }
+ }
+
+ /**
+ * Returns the element with the specified key, or null if not found.
+ * @param key the requested element's key
+ * @return the element with the specified key, or null if not found.
+ */
+ public KeyedElement getByKey(Object key) {
+ if (elementCount == 0)
+ return null;
+ int hash = keyHash(key);
+
+ // search the last half of the array
+ for (int i = hash; i < elements.length; i++) {
+ KeyedElement element = elements[i];
+ if (element == null)
+ return null;
+ if (element.getKey().equals(key))
+ return element;
+ }
+
+ // search the beginning of the array
+ for (int i = 0; i < hash - 1; i++) {
+ KeyedElement element = elements[i];
+ if (element == null)
+ return null;
+ if (element.getKey().equals(key))
+ return element;
+ }
+
+ // nothing found so return null
+ return null;
+ }
+
+ /**
+ * Returns the element which compares to the specified element, or null if not found.
+ * @see KeyedElement#compare(KeyedElement)
+ * @param otherElement the requested element
+ * @return the element which compares to the specified element, or null if not found.
+ */
+ public KeyedElement get(KeyedElement otherElement) {
+ if (elementCount == 0)
+ return null;
+ int hash = hash(otherElement);
+
+ // search the last half of the array
+ for (int i = hash; i < elements.length; i++) {
+ KeyedElement element = elements[i];
+ if (element == null)
+ return null;
+ if (element.compare(otherElement))
+ return element;
+ }
+
+ // search the beginning of the array
+ for (int i = 0; i < hash - 1; i++) {
+ KeyedElement element = elements[i];
+ if (element == null)
+ return null;
+ if (element.compare(otherElement))
+ return element;
+ }
+
+ // nothing found so return null
+ return null;
+ }
+
+ /**
+ * Returns true if this set is empty
+ * @return true if this set is empty
+ */
+ public boolean isEmpty() {
+ return elementCount == 0;
+ }
+
+ /**
+ * The element at the given index has been removed so move
+ * elements to keep the set properly hashed.
+ * @param anIndex the index that has been removed
+ */
+ protected void rehashTo(int anIndex) {
+
+ int target = anIndex;
+ int index = anIndex + 1;
+ if (index >= elements.length)
+ index = 0;
+ KeyedElement element = elements[index];
+ while (element != null) {
+ int hashIndex = hash(element);
+ boolean match;
+ if (index < target)
+ match = !(hashIndex > target || hashIndex <= index);
+ else
+ match = !(hashIndex > target && hashIndex <= index);
+ if (match) {
+ elements[target] = element;
+ target = index;
+ }
+ index++;
+ if (index >= elements.length)
+ index = 0;
+ element = elements[index];
+ }
+ elements[target] = null;
+ }
+
+ /**
+ * Removes the element with the specified key
+ * @param key the requested element's key
+ * @return true if an element was removed
+ */
+ public boolean removeByKey(Object key) {
+ if (elementCount == 0)
+ return false;
+ int hash = keyHash(key);
+
+ for (int i = hash; i < elements.length; i++) {
+ KeyedElement element = elements[i];
+ if (element == null)
+ return false;
+ if (element.getKey().equals(key)) {
+ rehashTo(i);
+ elementCount--;
+ return true;
+ }
+ }
+
+ for (int i = 0; i < hash - 1; i++) {
+ KeyedElement element = elements[i];
+ if (element == null)
+ return false;
+ if (element.getKey().equals(key)) {
+ rehashTo(i);
+ elementCount--;
+ return true;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Removes the element which compares to the specified element
+ * @param toRemove the requested element to remove
+ * @return true if an element was removed
+ */
+ public boolean remove(KeyedElement toRemove) {
+ if (elementCount == 0)
+ return false;
+
+ int hash = hash(toRemove);
+
+ for (int i = hash; i < elements.length; i++) {
+ KeyedElement element = elements[i];
+ if (element == null)
+ return false;
+ if (element.compare(toRemove)) {
+ rehashTo(i);
+ elementCount--;
+ return true;
+ }
+ }
+
+ for (int i = 0; i < hash - 1; i++) {
+ KeyedElement element = elements[i];
+ if (element == null)
+ return false;
+ if (element.compare(toRemove)) {
+ rehashTo(i);
+ elementCount--;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private int hash(KeyedElement element) {
+ return Math.abs(element.getKeyHashCode()) % elements.length;
+ }
+
+ private int keyHash(Object key) {
+ return Math.abs(key.hashCode()) % elements.length;
+ }
+
+ /**
+ * Removes all of the specified elements from this set
+ * @param toRemove the requested elements to remove
+ */
+ public void removeAll(KeyedElement[] toRemove) {
+ for (int i = 0; i < toRemove.length; i++)
+ remove(toRemove[i]);
+ }
+
+ private boolean shouldGrow() {
+ return elementCount > elements.length * 0.75;
+ }
+
+ /**
+ * Returns the number of elements in this set
+ * @return the number of elements in this set
+ */
+ public int size() {
+ return elementCount;
+ }
+
+ public String toString() {
+ StringBuffer result = new StringBuffer(100);
+ result.append("{"); //$NON-NLS-1$
+ boolean first = true;
+ for (int i = 0; i < elements.length; i++) {
+ if (elements[i] != null) {
+ if (first)
+ first = false;
+ else
+ result.append(", "); //$NON-NLS-1$
+ result.append(elements[i]);
+ }
+ }
+ result.append("}"); //$NON-NLS-1$
+ return result.toString();
+ }
+
+ /**
+ * Returns the number of collisions this set currently has
+ * @return the number of collisions this set currently has
+ */
+ public int countCollisions() {
+ int result = 0;
+ int lastHash = 0;
+ boolean found = false;
+ for (int i = 0; i < elements.length; i++) {
+ KeyedElement element = elements[i];
+ if (element == null)
+ found = false;
+ else {
+ int hash = hash(element);
+ if (found)
+ if (lastHash == hash)
+ result++;
+ else
+ found = false;
+ else {
+ lastHash = hash;
+ found = true;
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns an iterator of elements in this set
+ * @return an iterator of elements in this set
+ */
+ public Iterator<KeyedElement> iterator() {
+ return new EquinoxSetIterator();
+ }
+
+ class EquinoxSetIterator implements Iterator<KeyedElement> {
+ private int currentIndex = -1;
+ private int found;
+
+ public boolean hasNext() {
+ return found < elementCount;
+ }
+
+ public KeyedElement next() {
+ if (!hasNext())
+ throw new NoSuchElementException();
+ while (++currentIndex < elements.length)
+ if (elements[currentIndex] != null) {
+ found++;
+ return elements[currentIndex];
+ }
+ // this would mean we have less elements than we thought
+ throw new NoSuchElementException();
+ }
+
+ public void remove() {
+ // as allowed by the API
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * Clears all elements from this set
+ */
+ public void clear() {
+ elements = new KeyedElement[Math.max(MINIMUM_SIZE, capacity * 2)];
+ elementCount = 0;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/util/ObjectPool.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/util/ObjectPool.java
new file mode 100644
index 000000000..45cb44c10
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/util/ObjectPool.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.framework.util;
+
+import java.lang.ref.WeakReference;
+import java.util.Map;
+import java.util.WeakHashMap;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.framework.debug.FrameworkDebugOptions;
+
+public class ObjectPool {
+ private static String OPTION_DEBUG_OBJECTPOOL_ADDS = Debug.ECLIPSE_OSGI + "/debug/objectPool/adds"; //$NON-NLS-1$
+ private static String OPTION_DEBUG_OBJECTPOOL_DUPS = Debug.ECLIPSE_OSGI + "/debug/objectPool/dups"; //$NON-NLS-1$
+ private static final boolean DEBUG_OBJECTPOOL_ADDS;
+ private static final boolean DEBUG_OBJECTPOOL_DUPS;
+ private static Map<Object, WeakReference<Object>> objectCache = new WeakHashMap<Object, WeakReference<Object>>();
+ static {
+ FrameworkDebugOptions dbgOptions = FrameworkDebugOptions.getDefault();
+ if (dbgOptions != null) {
+ DEBUG_OBJECTPOOL_ADDS = dbgOptions.getBooleanOption(OPTION_DEBUG_OBJECTPOOL_ADDS, false);
+ DEBUG_OBJECTPOOL_DUPS = dbgOptions.getBooleanOption(OPTION_DEBUG_OBJECTPOOL_DUPS, false);
+ } else {
+ DEBUG_OBJECTPOOL_ADDS = false;
+ DEBUG_OBJECTPOOL_DUPS = false;
+ }
+ }
+
+ public static Object intern(Object obj) {
+ synchronized (objectCache) {
+ WeakReference<Object> ref = objectCache.get(obj);
+ if (ref != null) {
+ Object refValue = ref.get();
+ if (refValue != null) {
+ obj = refValue;
+ if (DEBUG_OBJECTPOOL_DUPS)
+ Debug.println("[ObjectPool] Found duplicate object: " + getObjectString(obj)); //$NON-NLS-1$
+ }
+ } else {
+ objectCache.put(obj, new WeakReference<Object>(obj));
+ if (DEBUG_OBJECTPOOL_ADDS)
+ Debug.println("[ObjectPool] Added unique object to pool: " + getObjectString(obj) + " Pool size: " + objectCache.size()); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+ return obj;
+ }
+
+ private static String getObjectString(Object obj) {
+ return "[(" + obj.getClass().getName() + ") " + obj.toString() + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/AdaptorMsg.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/AdaptorMsg.java
new file mode 100644
index 000000000..751119b08
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/AdaptorMsg.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.baseadaptor;
+
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Commom framework adaptor messages.
+ * <p>
+ * Clients may not extend this class
+ * </p>
+ * @since 3.1
+ */
+public class AdaptorMsg extends NLS {
+
+ public static String ADAPTER_FILEEXIST_EXCEPTION;
+ public static String ADAPTOR_DIRECTORY_CREATE_EXCEPTION;
+ public static String ADAPTOR_DIRECTORY_EXCEPTION;
+ public static String ADAPTOR_EXTENSION_IMPORT_ERROR;
+ public static String ADAPTOR_EXTENSION_NATIVECODE_ERROR;
+ public static String ADAPTOR_EXTENSION_REQUIRE_ERROR;
+ public static String ADAPTOR_STORAGE_EXCEPTION;
+ public static String ADAPTOR_STORAGE_INIT_FAILED_MSG;
+ public static String ADAPTOR_STORAGE_INIT_FAILED_TITLE;
+ public static String ADAPTOR_URL_CREATE_EXCEPTION;
+
+ public static String BUNDLE_CLASSPATH_ENTRY_NOT_FOUND_EXCEPTION;
+ public static String BUNDLE_NATIVECODE_EXCEPTION;
+ public static String BUNDLE_READ_EXCEPTION;
+
+ public static String MANIFEST_NOT_FOUND_EXCEPTION;
+
+ public static String RESOURCE_NOT_FOUND_EXCEPTION;
+
+ public static String SYSTEMBUNDLE_MISSING_MANIFEST;
+ public static String SYSTEMBUNDLE_NOTRESOLVED;
+
+ public static String URL_INVALID_BUNDLE_ID;
+ public static String URL_NO_BUNDLE_FOUND;
+ public static String URL_NO_BUNDLE_ID;
+
+ private static final String BUNDLE_NAME = "org.eclipse.osgi.internal.baseadaptor.ExternalMessages"; //$NON-NLS-1$
+
+ static {
+ // initialize resource bundles
+ NLS.initializeMessages(BUNDLE_NAME, AdaptorMsg.class);
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/AdaptorUtil.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/AdaptorUtil.java
new file mode 100644
index 000000000..3ff383ca4
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/AdaptorUtil.java
@@ -0,0 +1,280 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.baseadaptor;
+
+import java.io.*;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import org.eclipse.core.runtime.internal.adaptor.EclipseAdaptorMsg;
+import org.eclipse.osgi.baseadaptor.BaseData;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.framework.internal.core.Constants;
+import org.eclipse.osgi.framework.util.Headers;
+import org.eclipse.osgi.framework.util.ObjectPool;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.*;
+
+/**
+ * A utility class with some generally useful static methods for adaptor hook implementations
+ */
+public class AdaptorUtil {
+ /** The NULL tag used in bundle storage */
+ public static final byte NULL = 0;
+ /** The OBJECT tag used in bundle storage */
+ public static final byte OBJECT = 1;
+
+ /**
+ * Does a recursive copy of one directory to another.
+ * @param inDir input directory to copy.
+ * @param outDir output directory to copy to.
+ * @throws IOException if any error occurs during the copy.
+ */
+ public static void copyDir(File inDir, File outDir) throws IOException {
+ String[] files = inDir.list();
+ if (files != null && files.length > 0) {
+ outDir.mkdir();
+ for (int i = 0; i < files.length; i++) {
+ File inFile = new File(inDir, files[i]);
+ File outFile = new File(outDir, files[i]);
+ if (inFile.isDirectory()) {
+ copyDir(inFile, outFile);
+ } else {
+ InputStream in = new FileInputStream(inFile);
+ readFile(in, outFile);
+ }
+ }
+ }
+ }
+
+ /**
+ * Read a file from an InputStream and write it to the file system.
+ *
+ * @param in InputStream from which to read. This stream will be closed by this method.
+ * @param file output file to create.
+ * @exception IOException
+ */
+ public static void readFile(InputStream in, File file) throws IOException {
+ FileOutputStream fos = null;
+ try {
+ fos = new FileOutputStream(file);
+
+ byte buffer[] = new byte[1024];
+ int count;
+ while ((count = in.read(buffer, 0, buffer.length)) > 0) {
+ fos.write(buffer, 0, count);
+ }
+ } catch (IOException e) {
+ if (Debug.DEBUG_GENERAL) {
+ Debug.println("Unable to read file"); //$NON-NLS-1$
+ Debug.printStackTrace(e);
+ }
+
+ throw e;
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException ee) {
+ // nothing to do here
+ }
+ }
+
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (IOException ee) {
+ // nothing to do here
+ }
+ }
+ }
+ }
+
+ /**
+ * This function performs the equivalent of "rm -r" on a file or directory.
+ *
+ * @param file file or directory to delete
+ * @return false is the specified files still exists, true otherwise.
+ */
+ public static boolean rm(File file) {
+ if (file.exists()) {
+ if (file.isDirectory()) {
+ String list[] = file.list();
+ if (list != null) {
+ int len = list.length;
+ for (int i = 0; i < len; i++) {
+ // we are doing a lot of garbage collecting here
+ rm(new File(file, list[i]));
+ }
+ }
+ }
+ if (Debug.DEBUG_GENERAL) {
+ if (file.isDirectory()) {
+ Debug.println("rmdir " + file.getPath()); //$NON-NLS-1$
+ } else {
+ Debug.println("rm " + file.getPath()); //$NON-NLS-1$
+ }
+ }
+
+ boolean success = file.delete();
+
+ if (Debug.DEBUG_GENERAL) {
+ if (!success) {
+ Debug.println(" rm failed!!"); //$NON-NLS-1$
+ }
+ }
+
+ return (success);
+ }
+ return (true);
+ }
+
+ public static String readString(DataInputStream in, boolean intern) throws IOException {
+ byte type = in.readByte();
+ if (type == NULL)
+ return null;
+ return intern ? in.readUTF().intern() : in.readUTF();
+ }
+
+ public static void writeStringOrNull(DataOutputStream out, String string) throws IOException {
+ if (string == null)
+ out.writeByte(NULL);
+ else {
+ out.writeByte(OBJECT);
+ out.writeUTF(string);
+ }
+ }
+
+ public static Version loadVersion(DataInputStream in) throws IOException {
+ String versionString = readString(in, false);
+ try {
+ //return Version.parseVersion(versionString);
+ return (Version) ObjectPool.intern(Version.parseVersion(versionString));
+ } catch (IllegalArgumentException e) {
+ return new InvalidVersion(versionString);
+ }
+ }
+
+ /**
+ * Register a service object.
+ * @param name the service class name
+ * @param service the service object
+ * @param context the registering bundle context
+ * @return the service registration object
+ */
+ public static ServiceRegistration<?> register(String name, Object service, BundleContext context) {
+ Dictionary<String, Object> properties = new Hashtable<String, Object>(7);
+ Dictionary<String, String> headers = context.getBundle().getHeaders();
+ properties.put(Constants.SERVICE_VENDOR, headers.get(Constants.BUNDLE_VENDOR));
+ properties.put(Constants.SERVICE_RANKING, new Integer(Integer.MAX_VALUE));
+ properties.put(Constants.SERVICE_PID, context.getBundle().getBundleId() + "." + service.getClass().getName()); //$NON-NLS-1$
+ return context.registerService(name, service, properties);
+ }
+
+ public static Dictionary<String, String> loadManifestFrom(BaseData bundledata) throws BundleException {
+ URL url = bundledata.getEntry(Constants.OSGI_BUNDLE_MANIFEST);
+ if (url == null)
+ return null;
+ try {
+ return Headers.parseManifest(url.openStream());
+ } catch (IOException e) {
+ throw new BundleException(NLS.bind(EclipseAdaptorMsg.ECLIPSE_DATA_ERROR_READING_MANIFEST, bundledata.getLocation()), BundleException.MANIFEST_ERROR, e);
+ }
+ }
+
+ public static boolean canWrite(File installDir) {
+ if (installDir.canWrite() == false)
+ return false;
+
+ if (!installDir.isDirectory())
+ return false;
+
+ File fileTest = null;
+ try {
+ // we use the .dll suffix to properly test on Vista virtual directories
+ // on Vista you are not allowed to write executable files on virtual directories like "Program Files"
+ fileTest = File.createTempFile("writtableArea", ".dll", installDir); //$NON-NLS-1$ //$NON-NLS-2$
+ } catch (IOException e) {
+ //If an exception occured while trying to create the file, it means that it is not writtable
+ return false;
+ } finally {
+ if (fileTest != null)
+ fileTest.delete();
+ }
+ return true;
+ }
+
+ @SuppressWarnings("deprecation")
+ public static URL encodeFileURL(File file) throws MalformedURLException {
+ try {
+ Method toURI = File.class.getMethod("toURI", (Class[]) null); //$NON-NLS-1$
+ Object uri = toURI.invoke(file, (Object[]) null);
+ Method toURL = uri.getClass().getMethod("toURL", (Class[]) null); //$NON-NLS-1$
+ return (URL) toURL.invoke(uri, (Object[]) null);
+ } catch (NoSuchMethodException e) {
+ // use toURL.
+ } catch (IllegalAccessException e) {
+ // use toURL.
+ } catch (InvocationTargetException e) {
+ if (e.getTargetException() instanceof MalformedURLException)
+ throw (MalformedURLException) e.getTargetException();
+ // use toURL
+ }
+ // TODO should we do this by hand ourselves?
+ return file.toURL();
+ }
+
+ public static byte[] getBytes(InputStream in, int length, int BUF_SIZE) throws IOException {
+ byte[] classbytes;
+ int bytesread = 0;
+ int readcount;
+ try {
+ if (length > 0) {
+ classbytes = new byte[length];
+ for (; bytesread < length; bytesread += readcount) {
+ readcount = in.read(classbytes, bytesread, length - bytesread);
+ if (readcount <= 0) /* if we didn't read anything */
+ break; /* leave the loop */
+ }
+ } else /* does not know its own length! */{
+ length = BUF_SIZE;
+ classbytes = new byte[length];
+ readloop: while (true) {
+ for (; bytesread < length; bytesread += readcount) {
+ readcount = in.read(classbytes, bytesread, length - bytesread);
+ if (readcount <= 0) /* if we didn't read anything */
+ break readloop; /* leave the loop */
+ }
+ byte[] oldbytes = classbytes;
+ length += BUF_SIZE;
+ classbytes = new byte[length];
+ System.arraycopy(oldbytes, 0, classbytes, 0, bytesread);
+ }
+ }
+ if (classbytes.length > bytesread) {
+ byte[] oldbytes = classbytes;
+ classbytes = new byte[bytesread];
+ System.arraycopy(oldbytes, 0, classbytes, 0, bytesread);
+ }
+ } finally {
+ try {
+ in.close();
+ } catch (IOException ee) {
+ // nothing to do here
+ }
+ }
+ return classbytes;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/ArrayMap.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/ArrayMap.java
new file mode 100644
index 000000000..4cd862ae5
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/ArrayMap.java
@@ -0,0 +1,184 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.baseadaptor;
+
+import java.util.*;
+import org.eclipse.osgi.service.resolver.extras.Sortable;
+
+/**
+ * Simple map when dealing with small amounts of entries.
+ * This class also provides a Collections view of the keys
+ *
+ * @param <K> The key type
+ * @param <V> the value type
+ */
+public class ArrayMap<K, V> implements Collection<K>, Sortable<K> {
+ final List<K> keys;
+ final List<V> values;
+
+ public ArrayMap(int initialCapacity) {
+ keys = new ArrayList<K>(initialCapacity);
+ values = new ArrayList<V>(initialCapacity);
+ }
+
+ /**
+ * Note that the keys and values are not copied. Changes to this ArrayMap
+ * will change the values of the keys and values Lists.
+ * @param keys
+ * @param values
+ */
+ public ArrayMap(List<K> keys, List<V> values) {
+ if (keys.size() != values.size())
+ throw new IllegalArgumentException("Keys and values size must be equal."); //$NON-NLS-1$
+ this.keys = keys;
+ this.values = values;
+ }
+
+ public V get(K key) {
+ int index = keys.indexOf(key);
+ if (index < 0)
+ return null;
+ return values.get(index);
+ }
+
+ public void put(K key, V value) {
+ int index = keys.indexOf(key);
+ if (index > 0) {
+ values.set(index, value);
+ } else {
+ keys.add(key);
+ values.add(value);
+ }
+ }
+
+ public boolean remove(Object key) {
+ int index = keys.indexOf(key);
+ if (index < 0)
+ return false;
+ keys.remove(index);
+ values.remove(index);
+ return true;
+ }
+
+ public void clear() {
+ keys.clear();
+ values.clear();
+ }
+
+ public List<K> getKeys() {
+ return new ArrayList<K>(keys);
+ }
+
+ public List<V> getValues() {
+ return new ArrayList<V>(values);
+ }
+
+ public int size() {
+ return keys.size();
+ }
+
+ public boolean isEmpty() {
+ return keys.isEmpty();
+ }
+
+ public boolean contains(Object o) {
+ return keys.contains(o);
+ }
+
+ public Iterator<K> iterator() {
+ final Iterator<K> keyIter = keys.iterator();
+ final Iterator<V> valueIter = values.iterator();
+
+ return new Iterator<K>() {
+ public boolean hasNext() {
+ return keyIter.hasNext();
+ }
+
+ public K next() {
+ valueIter.next();
+ return keyIter.next();
+ }
+
+ public void remove() {
+ valueIter.remove();
+ keyIter.remove();
+ }
+ };
+ }
+
+ public Object[] toArray() {
+ return keys.toArray();
+ }
+
+ public <T> T[] toArray(T[] a) {
+ return keys.toArray(a);
+ }
+
+ public boolean add(K o) {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean containsAll(Collection<?> c) {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean addAll(Collection<? extends K> c) {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean removeAll(Collection<?> c) {
+ boolean result = false;
+ for (Object key : c)
+ result |= remove(key);
+ return result;
+ }
+
+ public boolean retainAll(Collection<?> c) {
+ boolean result = false;
+ Object[] keyArray = keys.toArray();
+ for (Object key : keyArray) {
+ if (!c.contains(key))
+ result |= remove(key);
+ }
+ return result;
+ }
+
+ public K getKey(int index) {
+ return keys.get(index);
+ }
+
+ public V getValue(int index) {
+ return values.get(index);
+ }
+
+ public void sort(Comparator<K> comparator) {
+ List<K> sortedKeys = new ArrayList<K>(keys);
+ Collections.sort(sortedKeys, comparator);
+ List<V> sortedValues = new ArrayList<V>(sortedKeys.size());
+ for (K key : sortedKeys) {
+ sortedValues.add(getByIdentity(key));
+ }
+ clear();
+ for (int i = 0; i < sortedKeys.size(); i++) {
+ put(sortedKeys.get(i), sortedValues.get(i));
+ }
+ }
+
+ private V getByIdentity(K key) {
+ int index = 0;
+ for (K existing : keys) {
+ if (existing == key)
+ return getValue(index);
+ index++;
+ }
+ return null;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BaseClassLoadingHook.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BaseClassLoadingHook.java
new file mode 100644
index 000000000..f5ffe0b8f
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BaseClassLoadingHook.java
@@ -0,0 +1,139 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.baseadaptor;
+
+import java.io.File;
+import java.security.ProtectionDomain;
+import java.util.ArrayList;
+import org.eclipse.osgi.baseadaptor.BaseData;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
+import org.eclipse.osgi.baseadaptor.hooks.ClassLoadingHook;
+import org.eclipse.osgi.baseadaptor.loader.*;
+import org.eclipse.osgi.framework.adaptor.BundleProtectionDomain;
+import org.eclipse.osgi.framework.adaptor.ClassLoaderDelegate;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.framework.internal.core.FrameworkProperties;
+import org.eclipse.osgi.util.ManifestElement;
+import org.osgi.framework.Constants;
+
+public class BaseClassLoadingHook implements ClassLoadingHook {
+ private static final String[] LIB_EXTENSIONS;
+ private static final String[] EMPTY_STRINGS = new String[0];
+ static {
+ String[] libExtensions = ManifestElement.getArrayFromList(FrameworkProperties.getProperty("osgi.framework.library.extensions", FrameworkProperties.getProperty(Constants.FRAMEWORK_LIBRARY_EXTENSIONS, getOSLibraryExtDefaults())), ","); //$NON-NLS-1$ //$NON-NLS-2$
+ for (int i = 0; i < libExtensions.length; i++)
+ if (libExtensions[i].length() > 0 && libExtensions[i].charAt(0) != '.')
+ libExtensions[i] = '.' + libExtensions[i];
+ LIB_EXTENSIONS = libExtensions;
+ }
+
+ private static String getOSLibraryExtDefaults() {
+ // Some OSes have multiple library extensions
+ // We should provide defaults to the known ones
+ // For example Mac OS X uses dylib and jnilib (bug 380350)
+ String os = FrameworkProperties.getProperty("os.name"); //$NON-NLS-1$
+ return os == null || !os.startsWith("Mac OS") ? null : "dylib,jnilib"; //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ /*
+ * Maps an already mapped library name to additional library file extensions.
+ * This is needed on platforms like AIX where .a and .so can be used as library file
+ * extensions, but System.mapLibraryName only returns a single string.
+ */
+ public static String[] mapLibraryNames(String mappedLibName) {
+ int extIndex = mappedLibName.lastIndexOf('.');
+ if (LIB_EXTENSIONS.length == 0 || extIndex < 0)
+ return EMPTY_STRINGS;
+ String libNameBase = mappedLibName.substring(0, extIndex);
+ String[] results = new String[LIB_EXTENSIONS.length];
+ for (int i = 0; i < results.length; i++)
+ results[i] = libNameBase + LIB_EXTENSIONS[i];
+ return results;
+ }
+
+ public String findLibrary(BaseData data, String libName) {
+ String mappedName = System.mapLibraryName(libName);
+ String path = null;
+ if (Debug.DEBUG_LOADER)
+ Debug.println(" mapped library name: " + mappedName); //$NON-NLS-1$
+ path = findNativePath(data, mappedName);
+ if (path == null) {
+ String[] mappedNames = mapLibraryNames(mappedName);
+ for (int i = 0; i < mappedNames.length && path == null; i++)
+ path = findNativePath(data, mappedNames[i]);
+ }
+ if (path == null) {
+ if (Debug.DEBUG_LOADER)
+ Debug.println(" library does not exist: " + mappedName); //$NON-NLS-1$
+ path = findNativePath(data, libName);
+ }
+ if (Debug.DEBUG_LOADER)
+ Debug.println(" returning library: " + path); //$NON-NLS-1$
+ return path;
+ }
+
+ private String findNativePath(BaseData bundledata, String libname) {
+ int slash = libname.lastIndexOf('/');
+ if (slash >= 0)
+ libname = libname.substring(slash + 1);
+ String[] nativepaths = getNativePaths(bundledata);
+ if (nativepaths == null)
+ return null;
+ for (int i = 0; i < nativepaths.length; i++) {
+ slash = nativepaths[i].lastIndexOf('/');
+ String path = slash < 0 ? nativepaths[i] : nativepaths[i].substring(slash + 1);
+ if (path.equals(libname)) {
+ if (nativepaths[i].startsWith(BaseStorageHook.EXTERNAL_LIB_PREFIX)) {
+ // references an external library; do variable substitution
+ String externalPath = BaseStorageHook.substituteVars(nativepaths[i].substring(BaseStorageHook.EXTERNAL_LIB_PREFIX.length()));
+ File nativeFile = new File(externalPath);
+ return nativeFile.getAbsolutePath();
+ }
+ // this is a normal library contained within the bundle
+ File nativeFile = bundledata.getBundleFile().getFile(nativepaths[i], true);
+ if (nativeFile != null)
+ return nativeFile.getAbsolutePath();
+ }
+ }
+ return null;
+ }
+
+ private String[] getNativePaths(BaseData bundledata) {
+ BaseStorageHook storageHook = (BaseStorageHook) bundledata.getStorageHook(BaseStorageHook.KEY);
+ return storageHook != null ? storageHook.getNativePaths() : null;
+ }
+
+ public boolean addClassPathEntry(ArrayList<ClasspathEntry> cpEntries, String cp, ClasspathManager hostmanager, BaseData sourcedata, ProtectionDomain sourcedomain) {
+ // do nothing
+ return false;
+ }
+
+ public ClassLoader getBundleClassLoaderParent() {
+ // do nothing
+ return null;
+ }
+
+ public byte[] processClass(String name, byte[] classbytes, ClasspathEntry classpathEntry, BundleEntry entry, ClasspathManager manager) {
+ // do nothing
+ return null;
+ }
+
+ public BaseClassLoader createClassLoader(ClassLoader parent, ClassLoaderDelegate delegate, BundleProtectionDomain domain, BaseData data, String[] bundleclasspath) {
+ // do nothing
+ return null;
+ }
+
+ public void initializedClassLoader(BaseClassLoader baseClassLoader, BaseData data) {
+ // do nothing
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BaseHookConfigurator.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BaseHookConfigurator.java
new file mode 100644
index 000000000..9e25d1e09
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BaseHookConfigurator.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (c) 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.baseadaptor;
+
+import org.eclipse.osgi.baseadaptor.HookConfigurator;
+import org.eclipse.osgi.baseadaptor.HookRegistry;
+
+/**
+ * Add the hooks necessary to support the OSGi Framework specification.
+ */
+public class BaseHookConfigurator implements HookConfigurator {
+
+ public void addHooks(HookRegistry registry) {
+ // always add the BaseStorageHook and BaseClassLoadingHook; it is required for the storage implementation
+ BaseStorageHook hook = new BaseStorageHook(new BaseStorage());
+ registry.addStorageHook(hook);
+ registry.addAdaptorHook(hook);
+ registry.addClassLoadingHook(new BaseClassLoadingHook());
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BasePermissionStorage.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BasePermissionStorage.java
new file mode 100644
index 000000000..120861572
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BasePermissionStorage.java
@@ -0,0 +1,97 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.baseadaptor;
+
+import java.io.IOException;
+import java.util.*;
+import org.eclipse.osgi.framework.adaptor.PermissionStorage;
+
+public class BasePermissionStorage implements PermissionStorage {
+
+ private Map<String, String[]> locations = new HashMap<String, String[]>();
+ private String[] defaultInfos;
+ private String[] condPermInfos;
+ private BaseStorage storage;
+ private boolean dirty;
+
+ BasePermissionStorage(BaseStorage storage) {
+ this.storage = storage;
+ }
+
+ /**
+ * @throws IOException
+ */
+ public String[] getLocations() throws IOException {
+ synchronized (locations) {
+ String[] result = new String[locations.size()];
+ int i = 0;
+ for (Iterator<String> iLocs = locations.keySet().iterator(); iLocs.hasNext(); i++)
+ result[i] = iLocs.next();
+ return result;
+ }
+ }
+
+ /**
+ * @throws IOException
+ */
+ public String[] getPermissionData(String location) throws IOException {
+ if (location == null)
+ return defaultInfos;
+ synchronized (locations) {
+ if (locations.size() == 0)
+ return null;
+ return locations.get(location);
+ }
+ }
+
+ /**
+ * @throws IOException
+ */
+ public void setPermissionData(String location, String[] data) throws IOException {
+ if (location == null) {
+ defaultInfos = data;
+ return;
+ }
+ synchronized (locations) {
+ if (data == null)
+ locations.remove(location);
+ else
+ locations.put(location, data);
+ }
+ setDirty(true);
+ storage.requestSave();
+ }
+
+ /**
+ * @throws IOException
+ */
+ public void saveConditionalPermissionInfos(String[] infos) throws IOException {
+ condPermInfos = infos;
+ setDirty(true);
+ storage.requestSave();
+ }
+
+ /**
+ * @throws IOException
+ */
+ public String[] getConditionalPermissionInfos() throws IOException {
+ return condPermInfos;
+ }
+
+ public boolean isDirty() {
+ return dirty;
+ }
+
+ public void setDirty(boolean dirty) {
+ this.dirty = dirty;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BaseStorage.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BaseStorage.java
new file mode 100644
index 000000000..cd6845667
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BaseStorage.java
@@ -0,0 +1,1450 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Rob Harrop - SpringSource Inc. (bug 247520 and 253942)
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.baseadaptor;
+
+import java.io.*;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.*;
+import java.util.*;
+import org.eclipse.core.runtime.adaptor.EclipseStarter;
+import org.eclipse.core.runtime.adaptor.LocationManager;
+import org.eclipse.core.runtime.internal.adaptor.EclipseAdaptorMsg;
+import org.eclipse.osgi.baseadaptor.BaseAdaptor;
+import org.eclipse.osgi.baseadaptor.BaseData;
+import org.eclipse.osgi.baseadaptor.bundlefile.*;
+import org.eclipse.osgi.baseadaptor.hooks.*;
+import org.eclipse.osgi.framework.adaptor.*;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.framework.debug.FrameworkDebugOptions;
+import org.eclipse.osgi.framework.internal.core.*;
+import org.eclipse.osgi.framework.internal.core.Constants;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+import org.eclipse.osgi.framework.util.KeyedHashSet;
+import org.eclipse.osgi.internal.loader.BundleLoader;
+import org.eclipse.osgi.internal.loader.BundleLoaderProxy;
+import org.eclipse.osgi.service.datalocation.Location;
+import org.eclipse.osgi.service.resolver.*;
+import org.eclipse.osgi.storagemanager.ManagedOutputStream;
+import org.eclipse.osgi.storagemanager.StorageManager;
+import org.eclipse.osgi.util.ManifestElement;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.*;
+
+public class BaseStorage implements SynchronousBundleListener {
+ private static final String RUNTIME_ADAPTOR = FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME + "/eclipseadaptor"; //$NON-NLS-1$
+ private static final String OPTION_PLATFORM_ADMIN = RUNTIME_ADAPTOR + "/debug/platformadmin"; //$NON-NLS-1$
+ private static final String OPTION_PLATFORM_ADMIN_RESOLVER = RUNTIME_ADAPTOR + "/debug/platformadmin/resolver"; //$NON-NLS-1$
+ private static final String OPTION_MONITOR_PLATFORM_ADMIN = RUNTIME_ADAPTOR + "/resolver/timing"; //$NON-NLS-1$
+ private static final String OPTION_RESOLVER_READER = RUNTIME_ADAPTOR + "/resolver/reader/timing"; //$NON-NLS-1$
+ private static final String PROP_FRAMEWORK_EXTENSIONS = "osgi.framework.extensions"; //$NON-NLS-1$
+ private static final String PROP_BUNDLE_STORE = "osgi.bundlestore"; //$NON-NLS-1$
+ // The name of the bundle data directory
+ static final String DATA_DIR_NAME = "data"; //$NON-NLS-1$
+ static final String LIB_TEMP = "libtemp"; //$NON-NLS-1$
+ // System property used to determine whether State saver needs to be enabled
+ private static final String PROP_ENABLE_STATE_SAVER = "eclipse.enableStateSaver"; //$NON-NLS-1$
+ static final String BUNDLEFILE_NAME = "bundlefile"; //$NON-NLS-1$
+ // System property used to clean the osgi configuration area
+ private static final String PROP_CLEAN = "osgi.clean"; //$NON-NLS-1$
+
+ /** The current bundle data version */
+ public static final byte BUNDLEDATA_VERSION = 18;
+ /**
+ * flag to indicate a framework extension is being intialized
+ */
+ public static final byte EXTENSION_INITIALIZE = 0x01;
+ /**
+ * flag to indicate a framework extension is being installed
+ */
+ public static final byte EXTENSION_INSTALLED = 0x02;
+ /**
+ * flag to indicate a framework extension is being uninstalled
+ */
+ public static final byte EXTENSION_UNINSTALLED = 0x04;
+ /**
+ * flag to indicate a framework extension is being updated
+ */
+ public static final byte EXTENSION_UPDATED = 0x08;
+
+ /** The BundleData is for a bundle exploded in a directory */
+ public static final int TYPE_DIRECTORYBUNDLE = 0x10000000;
+ /** The BundleData is for a bundle contained in a file (typically jar) */
+ public static final int TYPE_FILEBUNDLE = 0x20000000;
+
+ /**
+ * the file name for the delete flag. If this file exists in one a directory
+ * under the bundle store area then it will be removed during the
+ * compact operation.
+ */
+ public static final String DELETE_FLAG = ".delete"; //$NON-NLS-1$
+ private static final String PERM_DATA_FILE = ".permdata"; //$NON-NLS-1$
+ private static final byte PERMDATA_VERSION = 1;
+
+ private final MRUBundleFileList mruList = new MRUBundleFileList();
+
+ BaseAdaptor adaptor;
+ // assume a file: installURL
+ private String installPath;
+ private StorageManager storageManager;
+ private StateManager stateManager;
+ // no need to synchronize on storageHooks because the elements are statically set in initialize
+ private KeyedHashSet storageHooks = new KeyedHashSet(5, false);
+ private BundleContext context;
+ private SynchronousBundleListener extensionListener;
+
+ /**
+ * The add URL method used to support framework extensions
+ */
+ private final Method addFwkURLMethod;
+ private final Method addExtURLMethod;
+
+ /**
+ * The list of configured framework extensions
+ */
+ private String[] configuredExtensions;
+
+ private long timeStamp = 0;
+ private int initialBundleStartLevel = 1;
+
+ private final Object nextIdMonitor = new Object();
+ private volatile long nextId = 1;
+ /**
+ * directory containing installed bundles
+ */
+ private File bundleStoreRoot;
+
+ private BasePermissionStorage permissionStorage;
+ private StateSaver stateSaver;
+ private boolean invalidState;
+ private boolean storageManagerClosed;
+
+ BaseStorage() {
+ // make constructor package private
+ // initialize the addXYZURLMethods to support framework extensions
+ addFwkURLMethod = findAddURLMethod(getFwkClassLoader(), "addURL"); //$NON-NLS-1$
+ addExtURLMethod = findAddURLMethod(getExtClassLoader(), "addURL"); //$NON-NLS-1$
+ }
+
+ public void initialize(BaseAdaptor initAdaptor) throws IOException {
+ this.adaptor = initAdaptor;
+ setDebugOptions();
+ if (Boolean.valueOf(FrameworkProperties.getProperty(BaseStorage.PROP_CLEAN)).booleanValue())
+ cleanOSGiCache();
+
+ // we need to set the install path as soon as possible so we can determine
+ // the absolute location of install relative URLs
+ Location installLoc = LocationManager.getInstallLocation();
+ if (installLoc != null) {
+ URL installURL = installLoc.getURL();
+ // assume install URL is file: based
+ installPath = installURL.getPath();
+ }
+ boolean readOnlyConfiguration = LocationManager.getConfigurationLocation().isReadOnly();
+ storageManager = initFileManager(LocationManager.getOSGiConfigurationDir(), readOnlyConfiguration ? "none" : null, readOnlyConfiguration); //$NON-NLS-1$
+ storageManagerClosed = false;
+ // initialize the storageHooks
+ StorageHook[] hooks = initAdaptor.getHookRegistry().getStorageHooks();
+ for (int i = 0; i < hooks.length; i++)
+ storageHooks.add(hooks[i]);
+ }
+
+ private static Method findAddURLMethod(ClassLoader cl, String name) {
+ if (cl == null)
+ return null;
+ return findMethod(cl.getClass(), name, new Class[] {URL.class});
+ }
+
+ // recursively searches a class and it's superclasses for a (potentially inaccessable) method
+ private static Method findMethod(Class<?> clazz, String name, Class<?>[] args) {
+ if (clazz == null)
+ return null; // ends the recursion when getSuperClass returns null
+ try {
+ Method result = clazz.getDeclaredMethod(name, args);
+ result.setAccessible(true);
+ return result;
+ } catch (NoSuchMethodException e) {
+ // do nothing look in super class below
+ } catch (SecurityException e) {
+ // if we do not have the permissions then we will not find the method
+ }
+ return findMethod(clazz.getSuperclass(), name, args);
+ }
+
+ private static void callAddURLMethod(ClassLoader cl, Method meth, URL arg) throws InvocationTargetException {
+ try {
+ meth.invoke(cl, new Object[] {arg});
+ } catch (Throwable t) {
+ throw new InvocationTargetException(t);
+ }
+ }
+
+ private ClassLoader getFwkClassLoader() {
+ return this.getClass().getClassLoader();
+ }
+
+ private ClassLoader getExtClassLoader() {
+ ClassLoader cl = ClassLoader.getSystemClassLoader();
+ ClassLoader extcl = cl.getParent();
+ while ((extcl != null) && (extcl.getParent() != null)) {
+ extcl = extcl.getParent();
+ }
+ return extcl;
+ }
+
+ private static void setDebugOptions() {
+ FrameworkDebugOptions options = FrameworkDebugOptions.getDefault();
+ // may be null if debugging is not enabled
+ if (options == null)
+ return;
+ StateManager.DEBUG = options != null;
+ StateManager.DEBUG_READER = options.getBooleanOption(OPTION_RESOLVER_READER, false);
+ StateManager.MONITOR_PLATFORM_ADMIN = options.getBooleanOption(OPTION_MONITOR_PLATFORM_ADMIN, false);
+ StateManager.DEBUG_PLATFORM_ADMIN = options.getBooleanOption(OPTION_PLATFORM_ADMIN, false);
+ StateManager.DEBUG_PLATFORM_ADMIN_RESOLVER = options.getBooleanOption(OPTION_PLATFORM_ADMIN_RESOLVER, false);
+ }
+
+ protected StorageManager initFileManager(File baseDir, String lockMode, boolean readOnly) throws IOException {
+ StorageManager sManager = new StorageManager(baseDir, lockMode, readOnly);
+ try {
+ sManager.open(!readOnly);
+ } catch (IOException ex) {
+ if (Debug.DEBUG_GENERAL) {
+ Debug.println("Error reading framework metadata: " + ex.getMessage()); //$NON-NLS-1$
+ Debug.printStackTrace(ex);
+ }
+ String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_FILEMANAGER_OPEN_ERROR, ex.getMessage());
+ FrameworkLogEntry logEntry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, message, 0, ex, null);
+ adaptor.getFrameworkLog().log(logEntry);
+ FrameworkProperties.setProperty(EclipseStarter.PROP_EXITCODE, "15"); //$NON-NLS-1$
+ String errorDialog = "<title>" + AdaptorMsg.ADAPTOR_STORAGE_INIT_FAILED_TITLE + "</title>" + NLS.bind(AdaptorMsg.ADAPTOR_STORAGE_INIT_FAILED_MSG, baseDir) + "\n" + ex.getMessage(); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ FrameworkProperties.setProperty(EclipseStarter.PROP_EXITDATA, errorDialog);
+ throw ex;
+ }
+ return sManager;
+ }
+
+ public boolean isReadOnly() {
+ return storageManager.isReadOnly();
+ }
+
+ public void compact() {
+ if (!isReadOnly())
+ compact(getBundleStoreRoot());
+ }
+
+ private void compact(File directory) {
+ if (Debug.DEBUG_GENERAL)
+ Debug.println("compact(" + directory.getPath() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ String list[] = directory.list();
+ if (list == null)
+ return;
+
+ int len = list.length;
+ for (int i = 0; i < len; i++) {
+ if (BaseStorage.DATA_DIR_NAME.equals(list[i]))
+ continue; /* do not examine the bundles data dir. */
+ File target = new File(directory, list[i]);
+ // if the file is a directory
+ if (!target.isDirectory())
+ continue;
+ File delete = new File(target, BaseStorage.DELETE_FLAG);
+ // and the directory is marked for delete
+ if (delete.exists()) {
+ // if rm fails to delete the directory and .delete was removed
+ if (!AdaptorUtil.rm(target) && !delete.exists()) {
+ try {
+ // recreate .delete
+ FileOutputStream out = new FileOutputStream(delete);
+ out.close();
+ } catch (IOException e) {
+ if (Debug.DEBUG_GENERAL)
+ Debug.println("Unable to write " + delete.getPath() + ": " + e.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+ } else {
+ compact(target); /* descend into directory */
+ }
+ }
+ }
+
+ /**
+ * @throws IOException
+ */
+ public long getFreeSpace() throws IOException {
+ // cannot implement this without native code!
+ return -1;
+ }
+
+ public File getDataFile(BaseData data, String path) {
+ BaseStorageHook storageHook = (BaseStorageHook) data.getStorageHook(BaseStorageHook.KEY);
+ if (storageHook == null)
+ return null;
+ return storageHook.getDataFile(path);
+ }
+
+ BaseAdaptor getAdaptor() {
+ return adaptor;
+ }
+
+ public void installNativeCode(BaseData data, String[] nativepaths) throws BundleException {
+ if (nativepaths.length > 0) {
+ BaseStorageHook storageHook = (BaseStorageHook) data.getStorageHook(BaseStorageHook.KEY);
+ if (storageHook != null)
+ storageHook.installNativePaths(nativepaths);
+ }
+ }
+
+ public Dictionary<String, String> loadManifest(BaseData data) throws BundleException {
+ return loadManifest(data, false);
+ }
+
+ public Dictionary<String, String> loadManifest(BaseData bundleData, boolean firstTime) throws BundleException {
+ Dictionary<String, String> result = null;
+ StorageHook[] dataStorageHooks = bundleData.getStorageHooks();
+ for (int i = 0; i < dataStorageHooks.length && result == null; i++)
+ result = dataStorageHooks[i].getManifest(firstTime);
+ if (result == null)
+ result = AdaptorUtil.loadManifestFrom(bundleData);
+ if (result == null)
+ throw new BundleException(NLS.bind(AdaptorMsg.MANIFEST_NOT_FOUND_EXCEPTION, Constants.OSGI_BUNDLE_MANIFEST, bundleData.getLocation()), BundleException.MANIFEST_ERROR);
+ return result;
+ }
+
+ public File getExtractFile(BaseData data, String path) {
+ BaseStorageHook storageHook = (BaseStorageHook) data.getStorageHook(BaseStorageHook.KEY);
+ if (storageHook == null)
+ return null;
+ // first check the child generation dir
+ File childGenDir = storageHook.getGenerationDir();
+ if (childGenDir != null) {
+ File childPath = new File(childGenDir, path);
+ if (childPath.exists())
+ return childPath;
+ }
+ // now check the parent
+ File parentGenDir = storageHook.getParentGenerationDir();
+ if (parentGenDir != null) {
+ // there is a parent generation check if the file exists
+ File parentPath = new File(parentGenDir, path);
+ if (parentPath.exists())
+ // only use the parent generation file if it exists; do not extract there
+ return parentPath;
+ }
+ // did not exist in both locations; create a file for extraction.
+ File bundleGenerationDir = storageHook.createGenerationDir();
+ /* if the generation dir exists, then we have place to cache */
+ if (bundleGenerationDir != null && bundleGenerationDir.exists())
+ return new File(bundleGenerationDir, path);
+ return null;
+ }
+
+ public BaseData[] getInstalledBundles() {
+ try {
+ return readBundleDatas();
+ } catch (Throwable t) {
+ // be safe here and throw out the results and start over
+ // otherwise this would result in a failed launch
+ FrameworkLogEntry logEntry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, "Error loading bundle datas. Recalculating cache.", 0, t, null); //$NON-NLS-1$
+ adaptor.getFrameworkLog().log(logEntry);
+ return null;
+ }
+ }
+
+ private BaseData[] readBundleDatas() {
+ InputStream bundleDataStream = findStorageStream(LocationManager.BUNDLE_DATA_FILE);
+ if (bundleDataStream == null)
+ return null;
+ try {
+ DataInputStream in = new DataInputStream(new BufferedInputStream(bundleDataStream));
+ try {
+ byte version = in.readByte();
+ if (version != BUNDLEDATA_VERSION)
+ return null;
+ timeStamp = in.readLong();
+ initialBundleStartLevel = in.readInt();
+ nextId = in.readLong();
+
+ int numStorageHooks = in.readInt();
+ StorageHook[] hooks = adaptor.getHookRegistry().getStorageHooks();
+ if (numStorageHooks != hooks.length)
+ return null; // must have the same number of storagehooks to properly read the data
+ for (int i = 0; i < numStorageHooks; i++) {
+ Object storageKey = hooks[i].getKey();
+ int storageVersion = hooks[i].getStorageVersion();
+ if (!storageKey.equals(in.readUTF()) || storageVersion != in.readInt())
+ return null; // some storage hooks have changed must throw the data away.
+ }
+
+ int bundleCount = in.readInt();
+ List<BaseData> result = new ArrayList<BaseData>(bundleCount);
+ long id = -1;
+ boolean bundleDiscarded = false;
+ for (int i = 0; i < bundleCount; i++) {
+ boolean error = false;
+ BaseData data = null;
+ try {
+ id = in.readLong();
+ if (id != 0) {
+ data = loadBaseData(id, in);
+ data.getBundleFile();
+ StorageHook[] dataStorageHooks = data.getStorageHooks();
+ for (int j = 0; j < dataStorageHooks.length; j++)
+ dataStorageHooks[j].validate();
+ if (Debug.DEBUG_GENERAL)
+ Debug.println("BundleData created: " + data); //$NON-NLS-1$
+ processExtension(data, EXTENSION_INITIALIZE);
+ result.add(data);
+ }
+ } catch (IllegalArgumentException e) {
+ // may be from data.getBundleFile()
+ bundleDiscarded = true;
+ error = true;
+ } catch (BundleException e) {
+ // should never happen
+ bundleDiscarded = true;
+ error = true;
+ } catch (IOException e) {
+ bundleDiscarded = true;
+ error = true;
+ if (Debug.DEBUG_GENERAL) {
+ Debug.println("Error reading framework metadata: " + e.getMessage()); //$NON-NLS-1$
+ Debug.printStackTrace(e);
+ }
+ }
+ if (error && data != null) {
+ BaseStorageHook storageHook = (BaseStorageHook) data.getStorageHook(BaseStorageHook.KEY);
+ storageHook.delete(true, BaseStorageHook.DEL_BUNDLE_STORE);
+ }
+ }
+ if (bundleDiscarded)
+ FrameworkProperties.setProperty(EclipseStarter.PROP_REFRESH_BUNDLES, "true"); //$NON-NLS-1$
+ return result.toArray(new BaseData[result.size()]);
+ } finally {
+ in.close();
+ }
+ } catch (IOException e) {
+ if (Debug.DEBUG_GENERAL) {
+ Debug.println("Error reading framework metadata: " + e.getMessage()); //$NON-NLS-1$
+ Debug.printStackTrace(e);
+ }
+ }
+ return null;
+ }
+
+ private StorageManager getStorageManager() {
+ if (storageManagerClosed)
+ try {
+ storageManager.open(!LocationManager.getConfigurationLocation().isReadOnly());
+ storageManagerClosed = false;
+ } catch (IOException e) {
+ String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_FILEMANAGER_OPEN_ERROR, e.getMessage());
+ FrameworkLogEntry logEntry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, message, 0, e, null);
+ adaptor.getFrameworkLog().log(logEntry);
+ }
+ return storageManager;
+ }
+
+ void saveAllData(boolean shutdown) {
+ if (Debug.DEBUG_GENERAL)
+ Debug.println("Saving framework data ..."); //$NON-NLS-1$
+ saveBundleDatas();
+ saveStateData(shutdown);
+ savePermissionStorage();
+ if (shutdown)
+ stateManager.stopDataManager();
+ }
+
+ private BasePermissionStorage readPermissionData() {
+ BasePermissionStorage result = new BasePermissionStorage(this);
+ InputStream permDataStream = findStorageStream(PERM_DATA_FILE);
+ if (permDataStream == null)
+ return result;
+ try {
+ DataInputStream in = new DataInputStream(new BufferedInputStream(permDataStream));
+ try {
+ if (PERMDATA_VERSION != in.readByte())
+ return result;
+ // read the default permissions first
+ int numPerms = in.readInt();
+ if (numPerms > 0) {
+ String[] perms = new String[numPerms];
+ for (int i = 0; i < numPerms; i++)
+ perms[i] = in.readUTF();
+ result.setPermissionData(null, perms);
+ }
+ int numLocs = in.readInt();
+ if (numLocs > 0)
+ for (int i = 0; i < numLocs; i++) {
+ String loc = in.readUTF();
+ numPerms = in.readInt();
+ String[] perms = new String[numPerms];
+ for (int j = 0; j < numPerms; j++)
+ perms[j] = in.readUTF();
+ result.setPermissionData(loc, perms);
+ }
+ int numCondPerms = in.readInt();
+ if (numCondPerms > 0) {
+ String[] condPerms = new String[numCondPerms];
+ for (int i = 0; i < numCondPerms; i++)
+ condPerms[i] = in.readUTF();
+ result.saveConditionalPermissionInfos(condPerms);
+ }
+ result.setDirty(false);
+ } finally {
+ in.close();
+ }
+ } catch (IOException e) {
+ adaptor.getFrameworkLog().log(new FrameworkEvent(FrameworkEvent.ERROR, context.getBundle(), e));
+ }
+ return result;
+ }
+
+ private void savePermissionStorage() {
+ if (permissionStorage == null || isReadOnly() || !permissionStorage.isDirty())
+ return;
+ if (Debug.DEBUG_GENERAL)
+ Debug.println("About to save permission data ..."); //$NON-NLS-1$
+ try {
+ ManagedOutputStream fmos = getStorageManager().getOutputStream(PERM_DATA_FILE);
+ DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fmos));
+ boolean error = true;
+ try {
+ out.writeByte(PERMDATA_VERSION);
+ // always write the default permissions first
+ String[] defaultPerms = permissionStorage.getPermissionData(null);
+ out.writeInt(defaultPerms == null ? 0 : defaultPerms.length);
+ if (defaultPerms != null)
+ for (int i = 0; i < defaultPerms.length; i++)
+ out.writeUTF(defaultPerms[i]);
+ String[] locations = permissionStorage.getLocations();
+ out.writeInt(locations == null ? 0 : locations.length);
+ if (locations != null)
+ for (int i = 0; i < locations.length; i++) {
+ out.writeUTF(locations[i]);
+ String[] perms = permissionStorage.getPermissionData(locations[i]);
+ out.writeInt(perms == null ? 0 : perms.length);
+ if (perms != null)
+ for (int j = 0; j < perms.length; j++)
+ out.writeUTF(perms[j]);
+ }
+ String[] condPerms = permissionStorage.getConditionalPermissionInfos();
+ out.writeInt(condPerms == null ? 0 : condPerms.length);
+ if (condPerms != null)
+ for (int i = 0; i < condPerms.length; i++)
+ out.writeUTF(condPerms[i]);
+ out.close();
+ permissionStorage.setDirty(false);
+ error = false;
+ } finally {
+ // if something happens, don't close a corrupt file
+ if (error) {
+ fmos.abort();
+ try {
+ out.close();
+ } catch (IOException e) {/*ignore*/
+ }
+ }
+ }
+ } catch (IOException e) {
+ adaptor.getFrameworkLog().log(new FrameworkEvent(FrameworkEvent.ERROR, context.getBundle(), e));
+ return;
+ }
+ }
+
+ private void saveBundleDatas() {
+ // the cache and the state match
+ if (stateManager == null || isReadOnly() || (timeStamp == stateManager.getSystemState().getTimeStamp() && !stateManager.saveNeeded()))
+ return;
+ if (Debug.DEBUG_GENERAL)
+ Debug.println("Saving bundle data ..."); //$NON-NLS-1$
+ try {
+ ManagedOutputStream fmos = getStorageManager().getOutputStream(LocationManager.BUNDLE_DATA_FILE);
+ DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fmos));
+ boolean error = true;
+ try {
+ out.writeByte(BUNDLEDATA_VERSION);
+ out.writeLong(stateManager.getSystemState().getTimeStamp());
+ out.writeInt(initialBundleStartLevel);
+ out.writeLong(nextId);
+
+ StorageHook[] hooks = adaptor.getHookRegistry().getStorageHooks();
+ out.writeInt(hooks.length);
+ for (int i = 0; i < hooks.length; i++) {
+ out.writeUTF((String) hooks[i].getKey());
+ out.writeInt(hooks[i].getStorageVersion());
+ }
+
+ Bundle[] bundles = context.getBundles();
+ out.writeInt(bundles.length);
+ for (int i = 0; i < bundles.length; i++) {
+ long id = bundles[i].getBundleId();
+ out.writeLong(id);
+ if (id != 0) {
+ BundleData data = ((org.eclipse.osgi.framework.internal.core.AbstractBundle) bundles[i]).getBundleData();
+ saveBaseData((BaseData) data, out);
+ }
+ }
+ out.close();
+ // update the 'timeStamp' after the changed Meta data is saved.
+ timeStamp = stateManager.getSystemState().getTimeStamp();
+ error = false;
+ } finally {
+ // if something happens, don't close a corrupt file
+ if (error) {
+ fmos.abort();
+ try {
+ out.close();
+ } catch (IOException e) {/*ignore*/
+ }
+ }
+ }
+ } catch (IOException e) {
+ adaptor.getFrameworkLog().log(new FrameworkEvent(FrameworkEvent.ERROR, context.getBundle(), e));
+ return;
+ }
+ }
+
+ private void cleanRemovalPendings(State systemState, BundleDescription[] removalPendings) {
+ if (removalPendings.length == 0)
+ return;
+ systemState.resolve(removalPendings);
+ for (int i = 0; i < removalPendings.length; i++) {
+ Object userObject = removalPendings[i].getUserObject();
+ if (userObject instanceof BundleLoaderProxy) {
+ BundleLoader.closeBundleLoader((BundleLoaderProxy) userObject);
+ try {
+ ((BundleLoaderProxy) userObject).getBundleData().close();
+ } catch (IOException e) {
+ // ignore
+ }
+ } else if (userObject instanceof BundleData) {
+ try {
+ ((BundleData) userObject).close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+ }
+
+ private void saveStateData(boolean shutdown) {
+ State systemState = stateManager.getSystemState();
+ if (shutdown && "true".equals(FrameworkProperties.getProperty("osgi.forcedRestart"))) //$NON-NLS-1$ //$NON-NLS-2$
+ // increment the state timestamp if a forced restart happened.
+ systemState.setTimeStamp(systemState.getTimeStamp() + 1);
+ BundleDescription[] removalPendings = systemState.getRemovalPending();
+ if (removalPendings.length > 0) {
+ if (!shutdown)
+ return; // never save if removal pending; unless we are shutting down
+ cleanRemovalPendings(systemState, removalPendings);
+ }
+ if (stateManager == null || isReadOnly() || !stateManager.saveNeeded())
+ return;
+ if (Debug.DEBUG_GENERAL)
+ Debug.println("Saving resolver state data ..."); //$NON-NLS-1$
+ File stateTmpFile = null;
+ File lazyTmpFile = null;
+ try {
+ stateTmpFile = File.createTempFile(LocationManager.STATE_FILE, ".new", LocationManager.getOSGiConfigurationDir()); //$NON-NLS-1$
+ lazyTmpFile = File.createTempFile(LocationManager.LAZY_FILE, ".new", LocationManager.getOSGiConfigurationDir()); //$NON-NLS-1$
+ if (shutdown)
+ stateManager.shutdown(stateTmpFile, lazyTmpFile);
+ else
+ synchronized (stateManager) {
+ stateManager.update(stateTmpFile, lazyTmpFile);
+ }
+ StorageManager curStorageManager = getStorageManager();
+ curStorageManager.lookup(LocationManager.STATE_FILE, true);
+ curStorageManager.lookup(LocationManager.LAZY_FILE, true);
+ curStorageManager.update(new String[] {LocationManager.STATE_FILE, LocationManager.LAZY_FILE}, new String[] {stateTmpFile.getName(), lazyTmpFile.getName()});
+ } catch (IOException e) {
+ adaptor.getFrameworkLog().log(new FrameworkEvent(FrameworkEvent.ERROR, context.getBundle(), e));
+ } finally {
+ if (stateTmpFile != null && stateTmpFile.exists())
+ stateTmpFile.delete();
+ if (lazyTmpFile != null && lazyTmpFile.exists())
+ lazyTmpFile.delete();
+ }
+ }
+
+ public PermissionStorage getPermissionStorage() {
+ if (permissionStorage == null)
+ permissionStorage = readPermissionData();
+ return permissionStorage;
+ }
+
+ public int getInitialBundleStartLevel() {
+ return initialBundleStartLevel;
+ }
+
+ public void setInitialBundleStartLevel(int value) {
+ this.initialBundleStartLevel = value;
+ requestSave();
+ }
+
+ public void save(BaseData data) {
+ if (data.isDirty()) {
+ timeStamp--; // Change the value of the timeStamp, as a marker that something changed.
+ requestSave();
+ data.setDirty(false);
+ }
+ }
+
+ public BundleOperation installBundle(String location, URLConnection source) {
+ BaseData data = createBaseData(getNextBundleId(), location);
+ return new BundleInstall(data, source, this);
+ }
+
+ public BundleOperation updateBundle(BaseData data, URLConnection source) {
+ return new BundleUpdate(data, source, this);
+ }
+
+ public BundleOperation uninstallBundle(BaseData data) {
+ return new BundleUninstall(data, this);
+ }
+
+ protected Object getBundleContent(BaseData bundledata) {
+ BaseStorageHook storageHook = (BaseStorageHook) bundledata.getStorageHook(BaseStorageHook.KEY);
+ if (storageHook == null)
+ throw new IllegalStateException();
+ return storageHook.isReference() ? new File(storageHook.getFileName()) : new File(storageHook.getGenerationDir(), storageHook.getFileName());
+ }
+
+ public BundleFile createBundleFile(Object content, BaseData data) throws IOException {
+ boolean base = false;
+ if (content == null) {
+ // this must be a request for the base bundlefile
+ base = true;
+ // get the content of this bundle
+ content = getBundleContent(data);
+ }
+ BundleFile result = data.getBundleFile(content, base);
+ if (result != null)
+ return result;
+ // Ask factories before doing the default behavior
+ BundleFileFactoryHook[] factories = adaptor.getHookRegistry().getBundleFileFactoryHooks();
+ for (int i = 0; i < factories.length && result == null; i++)
+ result = factories[i].createBundleFile(content, data, base);
+
+ // No factories configured or they declined to create the bundle file; do default
+ if (result == null && content instanceof File) {
+ File file = (File) content;
+ if (isDirectory(data, base, file))
+ result = new DirBundleFile(file);
+ else
+ result = new ZipBundleFile(file, data, mruList);
+ }
+
+ if (result == null && content instanceof String) {
+ // here we assume the content is a path offset into the base bundle file; create a NestedDirBundleFile
+ result = new NestedDirBundleFile(data.getBundleFile(), (String) content);
+ }
+ if (result == null)
+ // nothing we can do; must throw exception for the content
+ throw new IOException("Cannot create bundle file for content of type: " + content.getClass().getName()); //$NON-NLS-1$
+
+ // try creating a wrapper bundlefile out of it.
+ BundleFileWrapperFactoryHook[] wrapperFactories = adaptor.getHookRegistry().getBundleFileWrapperFactoryHooks();
+ BundleFileWrapperChain wrapped = wrapperFactories.length == 0 ? null : new BundleFileWrapperChain(result, null);
+ for (int i = 0; i < wrapperFactories.length; i++) {
+ BundleFile wrapperBundle = wrapperFactories[i].wrapBundleFile(result, content, data, base);
+ if (wrapperBundle != null && wrapperBundle != result)
+ result = wrapped = new BundleFileWrapperChain(wrapperBundle, wrapped);
+ }
+ if (!base)
+ data.setBundleFile(content, result);
+ return result;
+ }
+
+ private boolean isDirectory(BaseData data, boolean base, File file) {
+ if (!base)
+ // there is no other place to check this; just consult the file directly
+ return file.isDirectory();
+ boolean isDirectory = false;
+ // first check if the type bits have been set
+ int type = data.getType();
+ if ((type & (TYPE_DIRECTORYBUNDLE | TYPE_FILEBUNDLE)) == 0) {
+ // no type bits set; consult the file and save the result
+ isDirectory = file.isDirectory();
+ data.setType(type | (isDirectory ? TYPE_DIRECTORYBUNDLE : TYPE_FILEBUNDLE));
+ } else {
+ // type bits have been set check to see if this is a directory bundle.
+ isDirectory = (type & TYPE_DIRECTORYBUNDLE) != 0;
+ }
+ return isDirectory;
+ }
+
+ public synchronized StateManager getStateManager() {
+ if (stateManager != null)
+ return stateManager;
+ stateManager = readStateData();
+ checkSystemState(stateManager.getSystemState());
+ return stateManager;
+ }
+
+ private void checkSystemState(State state) {
+ BundleDescription[] bundles = state.getBundles();
+ if (bundles == null)
+ return;
+ boolean removedBundle = false;
+ for (int i = 0; i < bundles.length; i++) {
+ if (adaptor.getBundle(bundles[i].getBundleId()) == null) {
+ state.removeBundle(bundles[i]);
+ removedBundle = true;
+ }
+ }
+ if (removedBundle)
+ state.resolve(false); // do a full resolve
+ BundleDescription systemBundle = state.getBundle(0);
+ if (systemBundle == null || !systemBundle.isResolved()) {
+ ResolverError[] errors = systemBundle == null ? new ResolverError[0] : state.getResolverErrors(systemBundle);
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < errors.length; i++) {
+ sb.append(errors[i].toString());
+ if (i < errors.length - 1)
+ sb.append(", "); //$NON-NLS-1$
+ }
+ // this would be a bug in the framework
+ throw new IllegalStateException(NLS.bind(AdaptorMsg.SYSTEMBUNDLE_NOTRESOLVED, sb.toString()));
+ }
+ }
+
+ private StateManager readStateData() {
+ File[] stateFiles = findStorageFiles(new String[] {LocationManager.STATE_FILE, LocationManager.LAZY_FILE});
+ File stateFile = stateFiles[0];
+ File lazyFile = stateFiles[1];
+
+ stateManager = new StateManager(stateFile, lazyFile, context, timeStamp);
+ State systemState = null;
+ if (!invalidState) {
+ systemState = stateManager.readSystemState();
+ if (systemState != null)
+ return stateManager;
+ }
+ systemState = stateManager.createSystemState();
+ Bundle[] installedBundles = context.getBundles();
+ if (installedBundles == null)
+ return stateManager;
+ StateObjectFactory factory = stateManager.getFactory();
+ for (int i = 0; i < installedBundles.length; i++) {
+ AbstractBundle toAdd = (AbstractBundle) installedBundles[i];
+ try {
+ // make sure we get the real manifest as if this is the first time.
+ Dictionary<String, String> toAddManifest = loadManifest((BaseData) toAdd.getBundleData(), true);
+ BundleDescription newDescription = factory.createBundleDescription(systemState, toAddManifest, toAdd.getLocation(), toAdd.getBundleId());
+ systemState.addBundle(newDescription);
+ } catch (BundleException be) {
+ // just ignore bundle datas with invalid manifests
+ }
+ }
+ // we do not set the cached timestamp here because we want a new one to be used from the new system state object (bug 132978)
+ // we need the state resolved
+ systemState.resolve();
+ invalidState = false;
+ return stateManager;
+ }
+
+ private File[] findStorageFiles(String[] fileNames) {
+ StorageManager curStorageManager = getStorageManager();
+ File[] storageFiles = new File[fileNames.length];
+ try {
+ for (int i = 0; i < storageFiles.length; i++)
+ storageFiles[i] = curStorageManager.lookup(fileNames[i], false);
+ } catch (IOException ex) {
+ if (Debug.DEBUG_GENERAL) {
+ Debug.println("Error reading state file " + ex.getMessage()); //$NON-NLS-1$
+ Debug.printStackTrace(ex);
+ }
+ }
+ boolean success = true;
+ for (int i = 0; i < storageFiles.length; i++)
+ if (storageFiles[i] == null || !storageFiles[i].isFile()) {
+ success = false;
+ break;
+ }
+ if (success)
+ return storageFiles;
+ //if it does not exist, try to read it from the parent
+ Location parentConfiguration = null;
+ Location currentConfiguration = LocationManager.getConfigurationLocation();
+ if (currentConfiguration != null && (parentConfiguration = currentConfiguration.getParentLocation()) != null) {
+ try {
+ File stateLocationDir = new File(parentConfiguration.getURL().getFile(), FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME);
+ StorageManager newFileManager = initFileManager(stateLocationDir, "none", true); //$NON-NLS-1$);
+ for (int i = 0; i < storageFiles.length; i++)
+ storageFiles[i] = newFileManager.lookup(fileNames[i], false);
+ newFileManager.close();
+ } catch (IOException ex) {
+ if (Debug.DEBUG_GENERAL) {
+ Debug.println("Error reading state file " + ex.getMessage()); //$NON-NLS-1$
+ Debug.printStackTrace(ex);
+ }
+ }
+ } else {
+ try {
+ //it did not exist in either place, so create it in the original location
+ if (!isReadOnly()) {
+ for (int i = 0; i < storageFiles.length; i++)
+ storageFiles[i] = curStorageManager.lookup(fileNames[i], true);
+ }
+ } catch (IOException ex) {
+ if (Debug.DEBUG_GENERAL) {
+ Debug.println("Error reading state file " + ex.getMessage()); //$NON-NLS-1$
+ Debug.printStackTrace(ex);
+ }
+ }
+ }
+ return storageFiles;
+ }
+
+ public void frameworkStart(BundleContext fwContext) {
+ this.context = fwContext;
+ // System property can be set to enable state saver or not.
+ if (Boolean.valueOf(FrameworkProperties.getProperty(BaseStorage.PROP_ENABLE_STATE_SAVER, "true")).booleanValue()) //$NON-NLS-1$
+ stateSaver = new StateSaver();
+
+ }
+
+ public void frameworkStop(BundleContext fwContext) {
+ if (stateSaver != null)
+ stateSaver.shutdown();
+ saveAllData(true);
+ storageManager.close();
+ storageManagerClosed = true;
+ if (extensionListener != null)
+ context.removeBundleListener(extensionListener);
+ mruList.shutdown();
+ stateManager = null;
+ }
+
+ public void frameworkStopping(BundleContext fwContext) {
+ // do nothing in storage
+ }
+
+ public void addProperties(Properties properties) {
+ // set the extension support if we found the addURL method
+ if (addFwkURLMethod != null)
+ properties.put(Constants.SUPPORTS_FRAMEWORK_EXTENSION, "true"); //$NON-NLS-1$
+ // store bundleStore back into adaptor properties for others to see
+ properties.put(BaseStorage.PROP_BUNDLE_STORE, getBundleStoreRoot().getAbsolutePath());
+ }
+
+ private InputStream findStorageStream(String fileName) {
+ InputStream storageStream = null;
+ try {
+ storageStream = getStorageManager().getInputStream(fileName);
+ } catch (IOException ex) {
+ if (Debug.DEBUG_GENERAL) {
+ Debug.println("Error reading framework metadata: " + ex.getMessage()); //$NON-NLS-1$
+ Debug.printStackTrace(ex);
+ }
+ }
+ if (storageStream == null) {
+ Location currentConfiguration = LocationManager.getConfigurationLocation();
+ Location parentConfiguration = null;
+ if (currentConfiguration != null && (parentConfiguration = currentConfiguration.getParentLocation()) != null) {
+ try {
+ File bundledataLocationDir = new File(parentConfiguration.getURL().getFile(), FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME);
+ StorageManager newStorageManager = initFileManager(bundledataLocationDir, "none", true); //$NON-NLS-1$
+ storageStream = newStorageManager.getInputStream(fileName);
+ newStorageManager.close();
+ } catch (MalformedURLException e1) {
+ // This will not happen since all the URLs are derived by us
+ // and we are GODS!
+ } catch (IOException e1) {
+ // That's ok we will regenerate the .bundleData
+ }
+ }
+ }
+ return storageStream;
+ }
+
+ protected void saveBaseData(BaseData bundledata, DataOutputStream out) throws IOException {
+ StorageHook[] hooks = bundledata.getStorageHooks();
+ out.writeInt(hooks.length);
+ for (int i = 0; i < hooks.length; i++) {
+ out.writeUTF((String) hooks[i].getKey());
+ hooks[i].save(out);
+ }
+ }
+
+ protected BaseData loadBaseData(long id, DataInputStream in) throws IOException {
+ BaseData result = new BaseData(id, adaptor);
+ int numHooks = in.readInt();
+ StorageHook[] hooks = new StorageHook[numHooks];
+ for (int i = 0; i < numHooks; i++) {
+ String hookKey = in.readUTF();
+ StorageHook storageHook = (StorageHook) storageHooks.getByKey(hookKey);
+ if (storageHook == null)
+ throw new IOException();
+ hooks[i] = storageHook.load(result, in);
+ }
+ result.setStorageHooks(hooks);
+ return result;
+ }
+
+ protected BaseData createBaseData(long id, String location) {
+ BaseData result = new BaseData(id, adaptor);
+ result.setLocation(location);
+ return result;
+ }
+
+ public String getInstallPath() {
+ return installPath;
+ }
+
+ private void cleanOSGiCache() {
+ File osgiConfig = LocationManager.getOSGiConfigurationDir();
+ if (!AdaptorUtil.rm(osgiConfig))
+ adaptor.getFrameworkLog().log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, "The -clean (osgi.clean) option was not successful. Unable to clean the storage area: " + osgiConfig.getAbsolutePath(), 0, null, null)); //$NON-NLS-1$
+
+ }
+
+ /**
+ * Processes an extension bundle
+ * @param bundleData the extension bundle data
+ * @param type the type of extension bundle
+ * @throws BundleException on any errors or if the extension bundle type is not supported
+ */
+ protected void processExtension(BaseData bundleData, byte type) throws BundleException {
+ if ((bundleData.getType() & BundleData.TYPE_FRAMEWORK_EXTENSION) != 0) {
+ validateExtension(bundleData);
+ processFrameworkExtension(bundleData, type);
+ } else if ((bundleData.getType() & BundleData.TYPE_BOOTCLASSPATH_EXTENSION) != 0) {
+ validateExtension(bundleData);
+ processBootExtension(bundleData, type);
+ } else if ((bundleData.getType() & BundleData.TYPE_EXTCLASSPATH_EXTENSION) != 0) {
+ validateExtension(bundleData);
+ processExtExtension(bundleData, type);
+ }
+ }
+
+ /**
+ * Validates the extension bundle metadata
+ * @param bundleData the extension bundle data
+ * @throws BundleException if the extension bundle metadata is invalid
+ */
+ private void validateExtension(BundleData bundleData) throws BundleException {
+ Dictionary<String, String> extensionManifest = bundleData.getManifest();
+ if (extensionManifest.get(Constants.IMPORT_PACKAGE) != null)
+ throw new BundleException(NLS.bind(AdaptorMsg.ADAPTOR_EXTENSION_IMPORT_ERROR, bundleData.getLocation()), BundleException.MANIFEST_ERROR);
+ if (extensionManifest.get(Constants.REQUIRE_BUNDLE) != null)
+ throw new BundleException(NLS.bind(AdaptorMsg.ADAPTOR_EXTENSION_REQUIRE_ERROR, bundleData.getLocation()), BundleException.MANIFEST_ERROR);
+ if (extensionManifest.get(Constants.BUNDLE_NATIVECODE) != null)
+ throw new BundleException(NLS.bind(AdaptorMsg.ADAPTOR_EXTENSION_NATIVECODE_ERROR, bundleData.getLocation()), BundleException.MANIFEST_ERROR);
+ }
+
+ /**
+ * Processes a framework extension bundle
+ * @param bundleData the extension bundle data
+ * @param type the type of extension bundle
+ * @throws BundleException on errors or if framework extensions are not supported
+ */
+ protected void processFrameworkExtension(BaseData bundleData, byte type) throws BundleException {
+ if (addFwkURLMethod == null)
+ throw new BundleException("Framework extensions are not supported.", BundleException.UNSUPPORTED_OPERATION, new UnsupportedOperationException()); //$NON-NLS-1$
+ addExtensionContent(bundleData, type, getFwkClassLoader(), addFwkURLMethod);
+ }
+
+ protected void processExtExtension(BaseData bundleData, byte type) throws BundleException {
+ if (addExtURLMethod == null)
+ throw new BundleException("Extension classpath extensions are not supported.", BundleException.UNSUPPORTED_OPERATION, new UnsupportedOperationException()); //$NON-NLS-1$
+ addExtensionContent(bundleData, type, getExtClassLoader(), addExtURLMethod);
+ }
+
+ private void addExtensionContent(BaseData bundleData, byte type, ClassLoader addToLoader, Method addToMethod) {
+ if ((type & (EXTENSION_UNINSTALLED | EXTENSION_UPDATED)) != 0)
+ // if uninstalled or updated then do nothing framework must be restarted.
+ return;
+
+ // first make sure this BundleData is not on the pre-configured osgi.framework.extensions list
+ String[] extensions = getConfiguredExtensions();
+ for (int i = 0; i < extensions.length; i++)
+ if (extensions[i].equals(bundleData.getSymbolicName()))
+ return;
+ if ((type & EXTENSION_INSTALLED) != 0) {
+ if (extensionListener == null) {
+ // add bundle listener to wait for extension to be resolved
+ extensionListener = this;
+ context.addBundleListener(extensionListener);
+ }
+ return;
+ }
+ File[] files = getExtensionFiles(bundleData);
+ if (files == null)
+ return;
+
+ for (int i = 0; i < files.length; i++) {
+ if (files[i] == null)
+ continue;
+ try {
+ callAddURLMethod(addToLoader, addToMethod, AdaptorUtil.encodeFileURL(files[i]));
+ } catch (InvocationTargetException e) {
+ adaptor.getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, bundleData.getBundle(), e);
+ } catch (MalformedURLException e) {
+ adaptor.getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, bundleData.getBundle(), e);
+ }
+ }
+
+ try {
+ addToLoader.loadClass("thisIsNotAClass"); // initialize the new urls //$NON-NLS-1$
+ } catch (ClassNotFoundException e) {
+ // do nothing
+ }
+ }
+
+ /**
+ * Returns a list of configured extensions
+ * @return a list of configured extensions
+ */
+ protected String[] getConfiguredExtensions() {
+ if (configuredExtensions != null)
+ return configuredExtensions;
+ String prop = FrameworkProperties.getProperty(BaseStorage.PROP_FRAMEWORK_EXTENSIONS);
+ if (prop == null || prop.trim().length() == 0)
+ configuredExtensions = new String[0];
+ else
+ configuredExtensions = ManifestElement.getArrayFromList(prop);
+ return configuredExtensions;
+ }
+
+ /**
+ * Processes a boot extension bundle
+ * @param bundleData the extension bundle data
+ * @param type the type of extension bundle
+ * @throws BundleException on errors or if boot extensions are not supported
+ */
+ protected void processBootExtension(BundleData bundleData, byte type) throws BundleException {
+ throw new BundleException("Boot classpath extensions are not supported.", BundleException.UNSUPPORTED_OPERATION, new UnsupportedOperationException()); //$NON-NLS-1$
+ }
+
+ private void initBundleStoreRoot() {
+ File configurationLocation = LocationManager.getOSGiConfigurationDir();
+ if (configurationLocation != null)
+ bundleStoreRoot = new File(configurationLocation, LocationManager.BUNDLES_DIR);
+ else
+ // last resort just default to "bundles"
+ bundleStoreRoot = new File(LocationManager.BUNDLES_DIR);
+ }
+
+ public File getBundleStoreRoot() {
+ if (bundleStoreRoot == null)
+ initBundleStoreRoot();
+ return bundleStoreRoot;
+ }
+
+ /**
+ * Returns a list of classpath files for an extension bundle
+ * @param bundleData the bundle data for an extension bundle
+ * @return a list of classpath files for an extension bundle
+ */
+ protected File[] getExtensionFiles(BaseData bundleData) {
+ File[] files = null;
+ try {
+ String[] paths = bundleData.getClassPath();
+ if (DevClassPathHelper.inDevelopmentMode()) {
+ String[] devPaths = DevClassPathHelper.getDevClassPath(bundleData.getSymbolicName());
+ String[] origPaths = paths;
+
+ paths = new String[origPaths.length + devPaths.length];
+ System.arraycopy(origPaths, 0, paths, 0, origPaths.length);
+ System.arraycopy(devPaths, 0, paths, origPaths.length, devPaths.length);
+ }
+ List<File> results = new ArrayList<File>(paths.length);
+ for (int i = 0; i < paths.length; i++) {
+ if (".".equals(paths[i])) //$NON-NLS-1$
+ results.add(bundleData.getBundleFile().getBaseFile());
+ else {
+ File result = bundleData.getBundleFile().getFile(paths[i], false);
+ if (result != null)
+ results.add(result);
+ }
+ }
+ return results.toArray(new File[results.size()]);
+ } catch (BundleException e) {
+ adaptor.getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, bundleData.getBundle(), e);
+ }
+ return files;
+ }
+
+ void requestSave() {
+ // Only when the State saver is enabled will the stateSaver be started.
+ if (stateSaver == null)
+ return;
+ stateSaver.requestSave();
+ }
+
+ /**
+ * Updates the state mananager with an updated/installed/uninstalled bundle
+ * @param bundleData the modified bundle
+ * @param type the type of modification
+ * @throws BundleException
+ */
+ public void updateState(BaseData bundleData, int type) throws BundleException {
+ if (stateManager == null) {
+ invalidState = true;
+ return;
+ }
+ State systemState = stateManager.getSystemState();
+ BundleDescription oldDescription = null;
+ BundleDescription newDescription = null;
+ switch (type) {
+ case BundleEvent.UPDATED :
+ // fall through to INSTALLED
+ case BundleEvent.INSTALLED :
+ if (type == BundleEvent.UPDATED)
+ oldDescription = systemState.getBundle(bundleData.getBundleID());
+ newDescription = stateManager.getFactory().createBundleDescription(systemState, bundleData.getManifest(), bundleData.getLocation(), bundleData.getBundleID());
+ // For the install case we need to set the bundle before adding to the state
+ // because the bundle is not available in the context yet.
+ // We go ahead and set it for the update case for simplicity;
+ // but this is not strictly necessary
+ newDescription.setUserObject(bundleData);
+ if (oldDescription == null)
+ systemState.addBundle(newDescription);
+ else
+ systemState.updateBundle(newDescription);
+ break;
+ case BundleEvent.UNINSTALLED :
+ systemState.removeBundle(bundleData.getBundleID());
+ break;
+ }
+
+ if (newDescription != null)
+ validateNativeCodePaths(newDescription, bundleData);
+ }
+
+ private void validateNativeCodePaths(BundleDescription newDescription, BaseData data) {
+ NativeCodeSpecification nativeCode = newDescription.getNativeCodeSpecification();
+ if (nativeCode == null)
+ return;
+ NativeCodeDescription nativeCodeDescs[] = nativeCode.getPossibleSuppliers();
+ for (int i = 0; i < nativeCodeDescs.length; i++) {
+ BaseStorageHook storageHook = (BaseStorageHook) data.getStorageHook(BaseStorageHook.KEY);
+ if (storageHook != null)
+ try {
+ storageHook.validateNativePaths(nativeCodeDescs[i].getNativePaths());
+ } catch (BundleException e) {
+ stateManager.getSystemState().setNativePathsInvalid(nativeCodeDescs[i], true);
+ }
+ }
+ }
+
+ private class StateSaver implements Runnable {
+ private final long delay_interval;
+ private final long max_total_delay_interval;
+ private boolean shutdown = false;
+ private long lastSaveTime = 0;
+ private Thread runningThread = null;
+ private Thread shutdownHook = null;
+
+ StateSaver() {
+ String prop = FrameworkProperties.getProperty("eclipse.stateSaveDelayInterval"); //$NON-NLS-1$
+ long delayValue = 30000; // 30 seconds.
+ long maxDelayValue = 1800000; // 30 minutes.
+ if (prop != null) {
+ try {
+ long val = Long.parseLong(prop);
+ if (val >= 1000 && val <= 1800000) {
+ delayValue = val;
+ maxDelayValue = val * 60;
+ } else if (val == 0) {
+ delayValue = 0;
+ maxDelayValue = 0;
+ }
+ } catch (NumberFormatException e) {
+ // ignore
+ }
+ }
+ delay_interval = delayValue;
+ max_total_delay_interval = maxDelayValue;
+ }
+
+ public void run() {
+ State systemState = adaptor.getState();
+ synchronized (systemState) {
+ long firstSaveTime = lastSaveTime;
+ long curSaveTime = 0;
+ long delayTime;
+ do {
+ do {
+ if ((System.currentTimeMillis() - firstSaveTime) > max_total_delay_interval) {
+ curSaveTime = lastSaveTime;
+ // Waiting time has been too long, so break to start saving State data to file.
+ break;
+ }
+ delayTime = Math.min(delay_interval, lastSaveTime - curSaveTime);
+ curSaveTime = lastSaveTime;
+ // wait for other save requests
+ try {
+ if (!shutdown)
+ systemState.wait(delayTime);
+ } catch (InterruptedException ie) {
+ // force break from do/while loops
+ curSaveTime = lastSaveTime;
+ break;
+ }
+ // Continue the loop if 'lastSaveTime' is increased again during waiting.
+ } while (!shutdown && curSaveTime < lastSaveTime);
+ // Save State and Meta data.
+ saveAllData(false);
+ // Continue the loop if Saver is asked again during saving State data to file.
+ } while (!shutdown && curSaveTime < lastSaveTime);
+ runningThread = null; // clear runningThread
+ try {
+ Runtime.getRuntime().removeShutdownHook(shutdownHook);
+ } catch (IllegalStateException e) {
+ // avoid exception if shutdown is in progress
+ }
+ shutdownHook = null;
+ }
+ }
+
+ void shutdown() {
+ State systemState = adaptor.getState();
+ Thread joinWith = null;
+ synchronized (systemState) {
+ shutdown = true;
+ joinWith = runningThread;
+ systemState.notifyAll(); // To wakeup sleeping thread.
+ }
+ try {
+ if (joinWith != null) {
+ if (Debug.DEBUG_GENERAL)
+ Debug.println("About to join saving thread"); //$NON-NLS-1$
+ // There should be no deadlock when 'shutdown' is true.
+ joinWith.join();
+ if (Debug.DEBUG_GENERAL)
+ Debug.println("Joined with saving thread"); //$NON-NLS-1$
+ }
+ } catch (InterruptedException ie) {
+ if (Debug.DEBUG_GENERAL) {
+ Debug.println("Error shutdowning StateSaver: " + ie.getMessage()); //$NON-NLS-1$
+ Debug.printStackTrace(ie);
+ }
+ }
+ }
+
+ void requestSave() {
+ final State systemState = adaptor.getState();
+ synchronized (systemState) {
+ if (shutdown)
+ return; // do not start another thread if we have already shutdown
+ if (delay_interval == 0) {
+ // all saves are atomic; never start a background thread
+ saveAllData(false);
+ return;
+ }
+ lastSaveTime = System.currentTimeMillis();
+ if (runningThread == null) {
+ shutdownHook = new Thread(new Runnable() {
+ public void run() {
+ // Synchronize with JVM shutdown hook, because
+ // saveAllData creates a temp file with delete on
+ // exit is true. The temp file will be removed in the
+ // shutdown hook. This prevents that the remove temp files
+ // in the shutdown hook is earlier handled then adding new
+ // temp file in saveAllData.
+ shutdown();
+ }
+ });
+ runningThread = new Thread(this, "State Saver"); //$NON-NLS-1$
+ runningThread.start();
+ try {
+ Runtime.getRuntime().addShutdownHook(shutdownHook);
+ } catch (IllegalStateException e) {
+ // bug 374300 - need to ignore this in case the VM is being shutdown
+ }
+ }
+ }
+ }
+ }
+
+ public long getNextBundleId() {
+ synchronized (this.nextIdMonitor) {
+ return nextId++;
+ }
+ }
+
+ public void bundleChanged(BundleEvent event) {
+ if (event.getType() != BundleEvent.RESOLVED)
+ return;
+ BaseData data = (BaseData) ((AbstractBundle) event.getBundle()).getBundleData();
+ try {
+ if ((data.getType() & BundleData.TYPE_FRAMEWORK_EXTENSION) != 0)
+ processFrameworkExtension(data, EXTENSION_INITIALIZE);
+ else if ((data.getType() & BundleData.TYPE_BOOTCLASSPATH_EXTENSION) != 0)
+ processBootExtension(data, EXTENSION_INITIALIZE);
+ else if ((data.getType() & BundleData.TYPE_EXTCLASSPATH_EXTENSION) != 0)
+ processExtExtension(data, EXTENSION_INITIALIZE);
+ } catch (BundleException e) {
+ // do nothing;
+ }
+ }
+
+ public String copyToTempLibrary(BaseData data, String absolutePath) throws IOException {
+ File storageRoot = getBundleStoreRoot();
+ File libTempDir = new File(storageRoot, LIB_TEMP);
+ // we assume the absolutePath is a File path
+ File realLib = new File(absolutePath);
+ String libName = realLib.getName();
+ // find a temp dir for the bundle data and the library;
+ File bundleTempDir = null;
+ File libTempFile = null;
+ // We need a somewhat predictable temp dir for the libraries of a given bundle;
+ // This is not strictly necessary but it does help scenarios where one native library loads another native library without using java.
+ // On some OSes this causes issues because the second library is cannot be found.
+ // This has been worked around by the bundles loading the libraries in a particular order (and setting some LIB_PATH env).
+ // The one catch is that the libraries need to be in the same directory and they must use their original lib names.
+ //
+ // This bit of code attempts to do that by using the bundle ID as an ID for the temp dir along with an incrementing ID
+ // in cases where the temp dir may already exist.
+ Long bundleID = new Long(data.getBundleID());
+ for (int i = 0; i < Integer.MAX_VALUE; i++) {
+ bundleTempDir = new File(libTempDir, bundleID.toString() + "_" + new Integer(i).toString()); //$NON-NLS-1$
+ libTempFile = new File(bundleTempDir, libName);
+ if (bundleTempDir.exists()) {
+ if (libTempFile.exists())
+ continue; // to to next temp file
+ break;
+ }
+ break;
+ }
+ if (!bundleTempDir.exists()) {
+ bundleTempDir.mkdirs();
+ bundleTempDir.deleteOnExit();
+ // This is just a safeguard incase the VM is terminated unexpectantly, it also looks like deleteOnExit cannot really work because
+ // the VM likely will still have a lock on the lib file at the time of VM exit.
+ File deleteFlag = new File(libTempDir, BaseStorage.DELETE_FLAG);
+ if (!deleteFlag.exists()) {
+ // need to create a delete flag to force removal the temp libraries
+ try {
+ FileOutputStream out = new FileOutputStream(deleteFlag);
+ out.close();
+ } catch (IOException e) {
+ // do nothing; that would mean we did not make the temp dir successfully
+ }
+ }
+ }
+ // copy the library file
+ InputStream in = new FileInputStream(realLib);
+ AdaptorUtil.readFile(in, libTempFile);
+ // set permissions if needed
+ BundleFile.setPermissions(libTempFile);
+ libTempFile.deleteOnExit(); // this probably will not work because the VM will probably have the lib locked at exit
+ // return the temporary path
+ return libTempFile.getAbsolutePath();
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BaseStorageHook.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BaseStorageHook.java
new file mode 100644
index 000000000..45bdeb6b2
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BaseStorageHook.java
@@ -0,0 +1,448 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Rob Harrop - SpringSource Inc. (bug 253942)
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.baseadaptor;
+
+import java.io.*;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.*;
+import org.eclipse.core.runtime.adaptor.LocationManager;
+import org.eclipse.osgi.baseadaptor.BaseAdaptor;
+import org.eclipse.osgi.baseadaptor.BaseData;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
+import org.eclipse.osgi.baseadaptor.hooks.AdaptorHook;
+import org.eclipse.osgi.baseadaptor.hooks.StorageHook;
+import org.eclipse.osgi.framework.adaptor.*;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.framework.internal.core.*;
+import org.eclipse.osgi.framework.internal.core.Constants;
+import org.eclipse.osgi.framework.log.FrameworkLog;
+import org.eclipse.osgi.framework.util.KeyedElement;
+import org.eclipse.osgi.service.datalocation.Location;
+import org.eclipse.osgi.util.ManifestElement;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.*;
+
+public class BaseStorageHook implements StorageHook, AdaptorHook {
+ public static final String KEY = BaseStorageHook.class.getName();
+ public static final int HASHCODE = KEY.hashCode();
+ public static final int DEL_BUNDLE_STORE = 0x01;
+ public static final int DEL_GENERATION = 0x02;
+ private static final int STORAGE_VERSION = 1;
+ public static final String EXTERNAL_LIB_PREFIX = "external:"; //$NON-NLS-1$
+ public static final String VARIABLE_DELIM_STRING = "$"; //$NON-NLS-1$
+ public static final char VARIABLE_DELIM_CHAR = '$';
+
+ /** bundle's file name */
+ private String fileName;
+ /** native code paths for this BundleData */
+ private String[] nativePaths;
+ /** bundle generation */
+ private int generation = 1;
+ /** Is bundle a reference */
+ private boolean reference;
+
+ private BaseData bundleData;
+ private BaseStorage storage;
+ private File bundleStore;
+ private File dataStore;
+
+ public BaseStorageHook(BaseStorage storage) {
+ this.storage = storage;
+ }
+
+ public int getStorageVersion() {
+ return STORAGE_VERSION;
+ }
+
+ /**
+ * @throws BundleException
+ */
+ public StorageHook create(BaseData bundledata) throws BundleException {
+ BaseStorageHook storageHook = new BaseStorageHook(storage);
+ storageHook.bundleData = bundledata;
+ return storageHook;
+ }
+
+ public void initialize(Dictionary<String, String> manifest) throws BundleException {
+ BaseStorageHook.loadManifest(bundleData, manifest);
+ }
+
+ @SuppressWarnings("deprecation")
+ static void loadManifest(BaseData target, Dictionary<String, String> manifest) throws BundleException {
+ try {
+ target.setVersion(Version.parseVersion(manifest.get(Constants.BUNDLE_VERSION)));
+ } catch (IllegalArgumentException e) {
+ target.setVersion(new InvalidVersion(manifest.get(Constants.BUNDLE_VERSION)));
+ }
+ ManifestElement[] bsnHeader = ManifestElement.parseHeader(Constants.BUNDLE_SYMBOLICNAME, manifest.get(Constants.BUNDLE_SYMBOLICNAME));
+ int bundleType = 0;
+ if (bsnHeader != null) {
+ target.setSymbolicName(bsnHeader[0].getValue());
+ String singleton = bsnHeader[0].getDirective(Constants.SINGLETON_DIRECTIVE);
+ if (singleton == null)
+ singleton = bsnHeader[0].getAttribute(Constants.SINGLETON_DIRECTIVE);
+ if ("true".equals(singleton)) //$NON-NLS-1$
+ bundleType |= BundleData.TYPE_SINGLETON;
+ }
+ // check that the classpath is valid
+ String classpath = manifest.get(Constants.BUNDLE_CLASSPATH);
+ ManifestElement.parseHeader(Constants.BUNDLE_CLASSPATH, classpath);
+ target.setClassPathString(classpath);
+ target.setActivator(manifest.get(Constants.BUNDLE_ACTIVATOR));
+ String host = manifest.get(Constants.FRAGMENT_HOST);
+ if (host != null) {
+ bundleType |= BundleData.TYPE_FRAGMENT;
+ ManifestElement[] hostElement = ManifestElement.parseHeader(Constants.FRAGMENT_HOST, host);
+ if (Constants.getInternalSymbolicName().equals(hostElement[0].getValue()) || Constants.SYSTEM_BUNDLE_SYMBOLICNAME.equals(hostElement[0].getValue())) {
+ String extensionType = hostElement[0].getDirective("extension"); //$NON-NLS-1$
+ if (extensionType == null || extensionType.equals("framework")) //$NON-NLS-1$
+ bundleType |= BundleData.TYPE_FRAMEWORK_EXTENSION;
+ else if (extensionType.equals("bootclasspath")) //$NON-NLS-1$
+ bundleType |= BundleData.TYPE_BOOTCLASSPATH_EXTENSION;
+ else if (extensionType.equals("extclasspath")) //$NON-NLS-1$
+ bundleType |= BundleData.TYPE_EXTCLASSPATH_EXTENSION;
+ }
+ }
+ target.setType(bundleType);
+ target.setExecutionEnvironment(manifest.get(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT));
+ target.setDynamicImports(manifest.get(Constants.DYNAMICIMPORT_PACKAGE));
+ }
+
+ public StorageHook load(BaseData target, DataInputStream in) throws IOException {
+ target.setLocation(AdaptorUtil.readString(in, false));
+ target.setSymbolicName(AdaptorUtil.readString(in, false));
+ target.setVersion(AdaptorUtil.loadVersion(in));
+ target.setActivator(AdaptorUtil.readString(in, false));
+ target.setClassPathString(AdaptorUtil.readString(in, false));
+ target.setExecutionEnvironment(AdaptorUtil.readString(in, false));
+ target.setDynamicImports(AdaptorUtil.readString(in, false));
+ target.setStartLevel(in.readInt());
+ target.setStatus(in.readInt());
+ target.setType(in.readInt());
+ target.setLastModified(in.readLong());
+ target.setDirty(false); // make sure to reset the dirty bit;
+
+ BaseStorageHook storageHook = new BaseStorageHook(storage);
+ storageHook.bundleData = target;
+ storageHook.generation = in.readInt();
+ storageHook.reference = in.readBoolean();
+ storageHook.setFileName(getAbsolute(storageHook.reference, AdaptorUtil.readString(in, false)));
+ int nativePathCount = in.readInt();
+ storageHook.nativePaths = nativePathCount > 0 ? new String[nativePathCount] : null;
+ for (int i = 0; i < nativePathCount; i++)
+ storageHook.nativePaths[i] = in.readUTF();
+ return storageHook;
+ }
+
+ private String getAbsolute(boolean isReference, String path) {
+ if (!isReference)
+ return path;
+ // fileName for bundles installed with reference URLs is stored relative to the install location
+ File storedPath = new File(path);
+ if (!storedPath.isAbsolute())
+ // make sure it has the absolute location instead
+ return new FilePath(storage.getInstallPath() + path).toString();
+ return path;
+ }
+
+ public void save(DataOutputStream out) throws IOException {
+ if (bundleData == null)
+ throw new IllegalStateException();
+ AdaptorUtil.writeStringOrNull(out, bundleData.getLocation());
+ AdaptorUtil.writeStringOrNull(out, bundleData.getSymbolicName());
+ AdaptorUtil.writeStringOrNull(out, bundleData.getVersion().toString());
+ AdaptorUtil.writeStringOrNull(out, bundleData.getActivator());
+ AdaptorUtil.writeStringOrNull(out, bundleData.getClassPathString());
+ AdaptorUtil.writeStringOrNull(out, bundleData.getExecutionEnvironment());
+ AdaptorUtil.writeStringOrNull(out, bundleData.getDynamicImports());
+ StorageHook[] hooks = bundleData.getStorageHooks();
+ boolean forgetStartLevel = false;
+ for (int i = 0; i < hooks.length && !forgetStartLevel; i++)
+ forgetStartLevel = hooks[i].forgetStartLevelChange(bundleData.getStartLevel());
+ out.writeInt(!forgetStartLevel ? bundleData.getStartLevel() : 1);
+ boolean forgetStatus = false;
+ // see if we should forget the persistently started flag
+ for (int i = 0; i < hooks.length && !forgetStatus; i++)
+ forgetStatus = hooks[i].forgetStatusChange(bundleData.getStatus());
+ out.writeInt(!forgetStatus ? bundleData.getStatus() : (~Constants.BUNDLE_STARTED) & bundleData.getStatus());
+ out.writeInt(bundleData.getType());
+ out.writeLong(bundleData.getLastModified());
+
+ out.writeInt(getGeneration());
+ out.writeBoolean(isReference());
+ String storedFileName = isReference() ? new FilePath(storage.getInstallPath()).makeRelative(new FilePath(getFileName())) : getFileName();
+ AdaptorUtil.writeStringOrNull(out, storedFileName);
+ if (nativePaths == null)
+ out.writeInt(0);
+ else {
+ out.writeInt(nativePaths.length);
+ for (int i = 0; i < nativePaths.length; i++)
+ out.writeUTF(nativePaths[i]);
+ }
+
+ }
+
+ public int getKeyHashCode() {
+ return HASHCODE;
+ }
+
+ public boolean compare(KeyedElement other) {
+ return other.getKey() == KEY;
+ }
+
+ public Object getKey() {
+ return KEY;
+ }
+
+ public String getFileName() {
+ return fileName;
+ }
+
+ public int getGeneration() {
+ return generation;
+ }
+
+ public String[] getNativePaths() {
+ return nativePaths;
+ }
+
+ public void installNativePaths(String[] installPaths) throws BundleException {
+ validateNativePaths(installPaths);
+ this.nativePaths = installPaths;
+ }
+
+ public void validateNativePaths(String[] paths) throws BundleException {
+ for (int i = 0; i < paths.length; i++) {
+ if (paths[i].startsWith(EXTERNAL_LIB_PREFIX)) {
+ String path = substituteVars(paths[i].substring(EXTERNAL_LIB_PREFIX.length()));
+ File nativeFile = new File(path);
+ if (!nativeFile.exists())
+ throw new BundleException(NLS.bind(AdaptorMsg.BUNDLE_NATIVECODE_EXCEPTION, nativeFile.getAbsolutePath()), BundleException.NATIVECODE_ERROR);
+ continue; // continue to next path
+ }
+ // ensure the file exists in the bundle; it will get extracted later on demand
+ BundleEntry nativeEntry = bundleData.getBundleFile().getEntry(paths[i]);
+ if (nativeEntry == null)
+ throw new BundleException(NLS.bind(AdaptorMsg.BUNDLE_NATIVECODE_EXCEPTION, paths[i]), BundleException.NATIVECODE_ERROR);
+ }
+ }
+
+ public boolean isReference() {
+ return reference;
+ }
+
+ public File getBundleStore() {
+ if (bundleStore == null)
+ bundleStore = new File(storage.getBundleStoreRoot(), String.valueOf(bundleData.getBundleID()));
+ return bundleStore;
+ }
+
+ public File getDataFile(String path) {
+ // lazily initialize dirData to prevent early access to configuration location
+ if (dataStore == null)
+ dataStore = new File(getBundleStore(), BaseStorage.DATA_DIR_NAME);
+ if (path != null && !dataStore.exists() && (storage.isReadOnly() || !dataStore.mkdirs()))
+ if (Debug.DEBUG_GENERAL)
+ Debug.println("Unable to create bundle data directory: " + dataStore.getPath()); //$NON-NLS-1$
+ return path == null ? dataStore : new File(dataStore, path);
+ }
+
+ void delete(boolean postpone, int type) throws IOException {
+ File delete = null;
+ switch (type) {
+ case DEL_GENERATION :
+ delete = getGenerationDir();
+ break;
+ case DEL_BUNDLE_STORE :
+ delete = getBundleStore();
+ break;
+ }
+ if (delete != null && delete.exists() && (postpone || !AdaptorUtil.rm(delete))) {
+ /* create .delete */
+ FileOutputStream out = new FileOutputStream(new File(delete, BaseStorage.DELETE_FLAG));
+ out.close();
+ }
+ }
+
+ File getGenerationDir() {
+ return new File(getBundleStore(), String.valueOf(getGeneration()));
+ }
+
+ File getParentGenerationDir() {
+ Location parentConfiguration = null;
+ Location currentConfiguration = LocationManager.getConfigurationLocation();
+ if (currentConfiguration != null && (parentConfiguration = currentConfiguration.getParentLocation()) != null)
+ return new File(parentConfiguration.getURL().getFile(), FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME + '/' + LocationManager.BUNDLES_DIR + '/' + bundleData.getBundleID() + '/' + getGeneration());
+ return null;
+ }
+
+ File createGenerationDir() {
+ File generationDir = getGenerationDir();
+ if (!generationDir.exists() && (storage.isReadOnly() || !generationDir.mkdirs()))
+ if (Debug.DEBUG_GENERAL)
+ Debug.println("Unable to create bundle generation directory: " + generationDir.getPath()); //$NON-NLS-1$
+ return generationDir;
+ }
+
+ public void setReference(boolean reference) {
+ this.reference = reference;
+ }
+
+ public void setFileName(String fileName) {
+ this.fileName = fileName;
+ // This is only done for PDE source lookup (bug 126517)
+ this.bundleData.setFileName(fileName);
+ }
+
+ public void copy(StorageHook storageHook) {
+ if (!(storageHook instanceof BaseStorageHook))
+ throw new IllegalArgumentException();
+ BaseStorageHook hook = (BaseStorageHook) storageHook;
+ bundleStore = hook.bundleStore;
+ dataStore = hook.dataStore;
+ generation = hook.generation + 1;
+ // fileName and reference will be set by update
+ }
+
+ public void validate() throws IllegalArgumentException {
+ // do nothing
+ }
+
+ /**
+ * @throws BundleException
+ */
+ public Dictionary<String, String> getManifest(boolean firstLoad) throws BundleException {
+ // do nothing
+ return null;
+ }
+
+ public boolean forgetStatusChange(int status) {
+ // do nothing
+ return false;
+ }
+
+ public boolean forgetStartLevelChange(int startlevel) {
+ // do nothing
+ return false;
+ }
+
+ public void initialize(BaseAdaptor adaptor) {
+ // do nothing
+ }
+
+ /**
+ * @throws BundleException
+ */
+ public void frameworkStart(BundleContext context) throws BundleException {
+ // do nothing
+ }
+
+ /**
+ * @throws BundleException
+ */
+ public void frameworkStop(BundleContext context) throws BundleException {
+ // do nothing
+ }
+
+ public void frameworkStopping(BundleContext context) {
+ // do nothing
+ }
+
+ public void addProperties(Properties properties) {
+ // do nothing
+ }
+
+ public URLConnection mapLocationToURLConnection(String location) throws IOException {
+ // take into account that initial@ is special (bug 268563)
+ if (location.startsWith("initial@")) { //$NON-NLS-1$
+ location = location.substring(8);
+ return new URL(location).openConnection();
+ }
+ // see if this is an existing location
+ Bundle[] bundles = storage.getAdaptor().getContext().getBundles();
+ AbstractBundle bundle = null;
+ for (int i = 0; i < bundles.length && bundle == null; i++)
+ if (location.equals(bundles[i].getLocation()))
+ bundle = (AbstractBundle) bundles[i];
+ if (bundle == null)
+ return null;
+ BaseData data = (BaseData) bundle.getBundleData();
+ BaseStorageHook hook = (BaseStorageHook) data.getStorageHook(BaseStorageHook.KEY);
+ return hook.isReference() ? new URL("reference:file:" + hook.getFileName()).openConnection() : null; //$NON-NLS-1$
+ }
+
+ public void handleRuntimeError(Throwable error) {
+ // do nothing
+ }
+
+ public FrameworkLog createFrameworkLog() {
+ // do nothing
+ return null;
+ }
+
+ public BaseStorage getStorage() {
+ return storage;
+ }
+
+ public static String substituteVars(String path) {
+ StringBuffer buf = new StringBuffer(path.length());
+ StringTokenizer st = new StringTokenizer(path, VARIABLE_DELIM_STRING, true);
+ boolean varStarted = false; // indicates we are processing a var subtitute
+ String var = null; // the current var key
+ while (st.hasMoreElements()) {
+ String tok = st.nextToken();
+ if (VARIABLE_DELIM_STRING.equals(tok)) {
+ if (!varStarted) {
+ varStarted = true; // we found the start of a var
+ var = ""; //$NON-NLS-1$
+ } else {
+ // we have found the end of a var
+ String prop = null;
+ // get the value of the var from system properties
+ if (var != null && var.length() > 0)
+ prop = FrameworkProperties.getProperty(var);
+ if (prop == null) {
+ try {
+ // try using the System.getenv method if it exists (bug 126921)
+ Method getenv = System.class.getMethod("getenv", new Class[] {String.class}); //$NON-NLS-1$
+ prop = (String) getenv.invoke(null, new Object[] {var});
+ } catch (Throwable t) {
+ // do nothing;
+ // on 1.4 VMs this throws an error
+ // on J2ME this method does not exist
+ }
+ }
+ if (prop != null)
+ // found a value; use it
+ buf.append(prop);
+ else
+ // could not find a value append the var name w/o delims
+ buf.append(var == null ? "" : var); //$NON-NLS-1$
+ varStarted = false;
+ var = null;
+ }
+ } else {
+ if (!varStarted)
+ buf.append(tok); // the token is not part of a var
+ else
+ var = tok; // the token is the var key; save the key to process when we find the end token
+ }
+ }
+ if (var != null)
+ // found a case of $var at the end of the path with no trailing $; just append it as is.
+ buf.append(VARIABLE_DELIM_CHAR).append(var);
+ return buf.toString();
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BundleInstall.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BundleInstall.java
new file mode 100644
index 000000000..82a8d13ec
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BundleInstall.java
@@ -0,0 +1,132 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.baseadaptor;
+
+import java.io.*;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Dictionary;
+import org.eclipse.osgi.baseadaptor.BaseData;
+import org.eclipse.osgi.baseadaptor.hooks.StorageHook;
+import org.eclipse.osgi.framework.adaptor.BundleData;
+import org.eclipse.osgi.framework.adaptor.BundleOperation;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.framework.internal.core.ReferenceInputStream;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.*;
+
+public class BundleInstall implements BundleOperation {
+ private BaseData data;
+ private URLConnection source;
+ private BaseStorage storage;
+
+ public BundleInstall(BaseData data, URLConnection source, BaseStorage storage) {
+ this.data = data;
+ this.source = source;
+ this.storage = storage;
+ }
+
+ /**
+ * Begin the operation on the bundle (install, update, uninstall).
+ *
+ * @return BundleData object for the target bundle.
+ * @throws BundleException If a failure occured modifiying peristent storage.
+ */
+ public BundleData begin() throws BundleException {
+ try {
+ InputStream in = null;
+ try {
+ data.setLastModified(System.currentTimeMillis());
+ data.setStartLevel(storage.getInitialBundleStartLevel());
+ StorageHook[] storageHooks = data.getAdaptor().getHookRegistry().getStorageHooks();
+ StorageHook[] instanceHooks = new StorageHook[storageHooks.length];
+ for (int i = 0; i < storageHooks.length; i++)
+ instanceHooks[i] = storageHooks[i].create(data);
+ data.setStorageHooks(instanceHooks);
+ BaseStorageHook storageHook = (BaseStorageHook) data.getStorageHook(BaseStorageHook.KEY);
+ in = source.getInputStream();
+ URL sourceURL = source.getURL();
+ String protocol = sourceURL == null ? null : sourceURL.getProtocol();
+ if (in instanceof ReferenceInputStream) {
+ URL reference = ((ReferenceInputStream) in).getReference();
+ if (!"file".equals(reference.getProtocol())) //$NON-NLS-1$
+ throw new BundleException(NLS.bind(AdaptorMsg.ADAPTOR_URL_CREATE_EXCEPTION, reference));
+ storageHook.setReference(true);
+ storageHook.setFileName(reference.getPath());
+ } else {
+ File genDir = storageHook.createGenerationDir();
+ if (!genDir.exists())
+ throw new IOException(NLS.bind(AdaptorMsg.ADAPTOR_DIRECTORY_CREATE_EXCEPTION, genDir.getPath()));
+ storageHook.setReference(false);
+ storageHook.setFileName(BaseStorage.BUNDLEFILE_NAME);
+ File outFile = new File(genDir, storageHook.getFileName());
+ if ("file".equals(protocol)) { //$NON-NLS-1$
+ File inFile = new File(source.getURL().getPath());
+ if (inFile.isDirectory())
+ AdaptorUtil.copyDir(inFile, outFile);
+ else
+ AdaptorUtil.readFile(in, outFile);
+ } else {
+ AdaptorUtil.readFile(in, outFile);
+ }
+ }
+ Dictionary<String, String> manifest = storage.loadManifest(data, true);
+ for (int i = 0; i < instanceHooks.length; i++)
+ instanceHooks[i].initialize(manifest);
+ } finally {
+ try {
+ if (in != null)
+ in.close();
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+ } catch (IOException ioe) {
+ throw new BundleException(AdaptorMsg.BUNDLE_READ_EXCEPTION, BundleException.READ_ERROR, ioe);
+ }
+
+ return (data);
+ }
+
+ public void undo() {
+ if (data != null) {
+ try {
+ data.close();
+ } catch (IOException e) {
+ if (Debug.DEBUG_GENERAL)
+ Debug.println("Unable to close " + data + ": " + e.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+
+ if (data != null) {
+ BaseStorageHook storageHook = (BaseStorageHook) data.getStorageHook(BaseStorageHook.KEY);
+ try {
+ if (storageHook != null)
+ storageHook.delete(false, BaseStorageHook.DEL_BUNDLE_STORE);
+ } catch (IOException e) {
+ data.getAdaptor().getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, data.getBundle(), e);
+ }
+ }
+ }
+
+ public void commit(boolean postpone) throws BundleException {
+ storage.processExtension(data, BaseStorage.EXTENSION_INSTALLED);
+ storage.updateState(data, BundleEvent.INSTALLED);
+ try {
+ data.save();
+ } catch (IOException e) {
+ throw new BundleException(AdaptorMsg.ADAPTOR_STORAGE_EXCEPTION, e);
+ }
+
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BundleUninstall.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BundleUninstall.java
new file mode 100644
index 000000000..83e1351aa
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BundleUninstall.java
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.baseadaptor;
+
+import java.io.IOException;
+import org.eclipse.osgi.baseadaptor.BaseData;
+import org.eclipse.osgi.framework.adaptor.BundleData;
+import org.eclipse.osgi.framework.adaptor.BundleOperation;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleException;
+
+public class BundleUninstall implements BundleOperation {
+ private BaseData data;
+ private BaseStorage storage;
+
+ public BundleUninstall(BaseData data, BaseStorage storage) {
+ this.data = data;
+ this.storage = storage;
+ }
+
+ /**
+ * Perform the change to persistent storage.
+ *
+ * @return Bundle object for the target bundle.
+ * @throws BundleException If a failure occured modifiying peristent storage.
+ */
+ public BundleData begin() throws BundleException {
+ return data;
+ }
+
+ /**
+ * Commit the change to persistent storage.
+ *
+ * @param postpone If true, the bundle's persistent
+ * storage cannot be immediately reclaimed.
+ * @throws BundleException If a failure occured modifiying peristent storage.
+ */
+ public void commit(boolean postpone) throws BundleException {
+ BaseStorageHook storageHook = (BaseStorageHook) data.getStorageHook(BaseStorageHook.KEY);
+ try {
+ storageHook.delete(postpone, BaseStorageHook.DEL_BUNDLE_STORE);
+ } catch (IOException e) {
+ // nothing we can do
+ }
+ storage.processExtension(data, BaseStorage.EXTENSION_UNINSTALLED);
+ data.setLastModified(System.currentTimeMillis());
+ storage.updateState(data, BundleEvent.UNINSTALLED);
+ data.setDirty(true);
+ try {
+ data.save();
+ } catch (IOException e) {
+ throw new BundleException(AdaptorMsg.ADAPTOR_STORAGE_EXCEPTION, e);
+ }
+ }
+
+ /**
+ * Undo the change to persistent storage.
+ *
+ * @throws BundleException If a failure occured modifiying peristent storage.
+ */
+ public void undo() throws BundleException {
+ // do nothing
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BundleUpdate.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BundleUpdate.java
new file mode 100644
index 000000000..732f25214
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/BundleUpdate.java
@@ -0,0 +1,151 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.baseadaptor;
+
+import java.io.*;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Dictionary;
+import org.eclipse.osgi.baseadaptor.BaseData;
+import org.eclipse.osgi.baseadaptor.hooks.StorageHook;
+import org.eclipse.osgi.framework.adaptor.BundleData;
+import org.eclipse.osgi.framework.adaptor.BundleOperation;
+import org.eclipse.osgi.framework.internal.core.ReferenceInputStream;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.*;
+
+public class BundleUpdate implements BundleOperation {
+ private BaseData data;
+ private BaseData newData;
+ private URLConnection source;
+ private BaseStorage storage;
+
+ public BundleUpdate(BaseData data, URLConnection source, BaseStorage storage) {
+ this.data = data;
+ this.source = source;
+ this.storage = storage;
+ }
+
+ /**
+ * Perform the change to persistent storage.
+ *
+ * @return Bundle object for the target bundle.
+ * @throws BundleException if an error occurs
+ */
+ public BundleData begin() throws BundleException {
+ try {
+ newData = storage.createBaseData(data.getBundleID(), data.getLocation());
+ newData.setLastModified(System.currentTimeMillis());
+ newData.setStartLevel(data.getStartLevel());
+ newData.setStatus(data.getStatus());
+ // load the storage hooks into the new data
+ StorageHook[] storageHooks = data.getAdaptor().getHookRegistry().getStorageHooks();
+ StorageHook[] instanceHooks = new StorageHook[storageHooks.length];
+ for (int i = 0; i < storageHooks.length; i++) {
+ instanceHooks[i] = storageHooks[i].create(newData);
+ instanceHooks[i].copy(data.getStorageHook((String) instanceHooks[i].getKey()));
+ }
+ newData.setStorageHooks(instanceHooks);
+ // get the new eclipse storage hooks
+ BaseStorageHook newStorageHook = (BaseStorageHook) newData.getStorageHook(BaseStorageHook.KEY);
+ InputStream in = source.getInputStream();
+ URL sourceURL = source.getURL();
+ String protocol = sourceURL == null ? null : sourceURL.getProtocol();
+ try {
+ if (in instanceof ReferenceInputStream) {
+ URL reference = ((ReferenceInputStream) in).getReference();
+ if (!"file".equals(reference.getProtocol())) //$NON-NLS-1$
+ throw new BundleException(NLS.bind(AdaptorMsg.ADAPTOR_URL_CREATE_EXCEPTION, reference));
+ // check to make sure we are not just trying to update to the same
+ // directory reference. This would be a no-op.
+ String path = reference.getPath();
+ newStorageHook.setReference(true);
+ newStorageHook.setFileName(path);
+ } else {
+ File genDir = newStorageHook.createGenerationDir();
+ if (!genDir.exists())
+ throw new BundleException(NLS.bind(AdaptorMsg.ADAPTOR_DIRECTORY_CREATE_EXCEPTION, genDir.getPath()));
+ newStorageHook.setReference(false);
+ newStorageHook.setFileName(BaseStorage.BUNDLEFILE_NAME);
+ File outFile = new File(genDir, newStorageHook.getFileName());
+ if ("file".equals(protocol)) { //$NON-NLS-1$
+ File inFile = new File(source.getURL().getPath());
+ if (inFile.isDirectory()) {
+ AdaptorUtil.copyDir(inFile, outFile);
+ } else {
+ AdaptorUtil.readFile(in, outFile);
+ }
+ } else {
+ AdaptorUtil.readFile(in, outFile);
+ }
+ }
+ Dictionary<String, String> manifest = storage.loadManifest(newData, true);
+ for (int i = 0; i < instanceHooks.length; i++)
+ instanceHooks[i].initialize(manifest);
+ } finally {
+ try {
+ if (in != null)
+ in.close();
+ } catch (IOException ee) {
+ // nothing to do here
+ }
+ }
+ } catch (IOException e) {
+ throw new BundleException(AdaptorMsg.BUNDLE_READ_EXCEPTION, BundleException.READ_ERROR, e);
+ }
+
+ return (newData);
+ }
+
+ /**
+ * Commit the change to persistent storage.
+ *
+ * @param postpone If true, the bundle's persistent
+ * storage cannot be immediately reclaimed.
+ * @throws BundleException If a failure occured modifiying peristent storage.
+ */
+
+ public void commit(boolean postpone) throws BundleException {
+ storage.processExtension(data, BaseStorage.EXTENSION_UNINSTALLED); // remove the old extension
+ storage.processExtension(newData, BaseStorage.EXTENSION_UPDATED); // update to the new one
+ newData.setLastModified(System.currentTimeMillis()); // save the last modified
+ storage.updateState(newData, BundleEvent.UPDATED);
+ try {
+ newData.save();
+ } catch (IOException e) {
+ throw new BundleException(AdaptorMsg.ADAPTOR_STORAGE_EXCEPTION, e);
+ }
+ BaseStorageHook oldStorageHook = (BaseStorageHook) data.getStorageHook(BaseStorageHook.KEY);
+ try {
+ oldStorageHook.delete(postpone, BaseStorageHook.DEL_GENERATION);
+ } catch (IOException e) {
+ data.getAdaptor().getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, data.getBundle(), e);
+ }
+ }
+
+ /**
+ * Undo the change to persistent storage.
+ *
+ * @throws BundleException If a failure occured modifiying peristent storage.
+ */
+ public void undo() throws BundleException {
+ if (newData != null) {
+ BaseStorageHook newStorageHook = (BaseStorageHook) newData.getStorageHook(BaseStorageHook.KEY);
+ try {
+ if (newStorageHook != null)
+ newStorageHook.delete(false, BaseStorageHook.DEL_GENERATION);
+ } catch (IOException e) {
+ data.getAdaptor().getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, data.getBundle(), e);
+ }
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/DefaultClassLoader.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/DefaultClassLoader.java
new file mode 100644
index 000000000..6d252fd95
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/DefaultClassLoader.java
@@ -0,0 +1,300 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.baseadaptor;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.*;
+import java.security.cert.Certificate;
+import java.util.*;
+import org.eclipse.osgi.baseadaptor.BaseData;
+import org.eclipse.osgi.baseadaptor.bundlefile.*;
+import org.eclipse.osgi.baseadaptor.loader.*;
+import org.eclipse.osgi.framework.adaptor.*;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.framework.internal.core.FrameworkProperties;
+import org.eclipse.osgi.signedcontent.SignedContent;
+import org.eclipse.osgi.signedcontent.SignerInfo;
+import org.osgi.framework.Bundle;
+
+/**
+ * The default implementation of <code>BaseClassLoader</code>. This implementation extends
+ * <code>ClassLoader</code>.
+ * @see BaseClassLoader
+ * @see ClasspathManager
+ */
+public class DefaultClassLoader extends ClassLoader implements ParallelClassLoader {
+ /**
+ * A PermissionCollection for AllPermissions; shared across all ProtectionDomains when security is disabled
+ */
+ protected static final PermissionCollection ALLPERMISSIONS;
+ private final static String CLASS_CERTIFICATE_SUPPORT = "osgi.support.class.certificate"; //$NON-NLS-1$
+ private final static String CLASS_LOADER_TYPE = "osgi.classloader.type"; //$NON-NLS-1$
+ private final static String CLASS_LOADER_TYPE_PARALLEL = "parallel"; //$NON-NLS-1$
+ private static final boolean CLASS_CERTIFICATE;
+ private static final boolean PARALLEL_CAPABLE;
+ @SuppressWarnings("unchecked")
+ private static final Enumeration<URL> EMPTY_ENUMERATION = Collections.enumeration(Collections.EMPTY_LIST);
+
+ static {
+ CLASS_CERTIFICATE = Boolean.valueOf(FrameworkProperties.getProperty(CLASS_CERTIFICATE_SUPPORT, "true")).booleanValue(); //$NON-NLS-1$
+ AllPermission allPerm = new AllPermission();
+ ALLPERMISSIONS = allPerm.newPermissionCollection();
+ if (ALLPERMISSIONS != null)
+ ALLPERMISSIONS.add(allPerm);
+ boolean typeParallel = CLASS_LOADER_TYPE_PARALLEL.equals(FrameworkProperties.getProperty(CLASS_LOADER_TYPE, CLASS_LOADER_TYPE_PARALLEL));
+ boolean parallelCapable = false;
+ try {
+ if (typeParallel) {
+ Method parallelCapableMetod = ClassLoader.class.getDeclaredMethod("registerAsParallelCapable", (Class[]) null); //$NON-NLS-1$
+ parallelCapableMetod.setAccessible(true);
+ parallelCapable = ((Boolean) parallelCapableMetod.invoke(null, (Object[]) null)).booleanValue();
+ }
+ } catch (Throwable e) {
+ // must do everything to avoid failing in clinit
+ parallelCapable = false;
+ }
+ PARALLEL_CAPABLE = parallelCapable;
+ }
+
+ protected ClassLoaderDelegate delegate;
+ protected ProtectionDomain domain;
+ // Note that PDE has internal dependency on this field type/name (bug 267238)
+ protected ClasspathManager manager;
+
+ /**
+ * Constructs a new DefaultClassLoader.
+ * @param parent the parent classloader
+ * @param delegate the delegate for this classloader
+ * @param domain the domain for this classloader
+ * @param bundledata the bundledata for this classloader
+ * @param classpath the classpath for this classloader
+ */
+ public DefaultClassLoader(ClassLoader parent, ClassLoaderDelegate delegate, ProtectionDomain domain, BaseData bundledata, String[] classpath) {
+ super(parent);
+ this.delegate = delegate;
+ this.domain = domain;
+ this.manager = new ClasspathManager(bundledata, classpath, this);
+ }
+
+ /**
+ * Loads a class for the bundle. First delegate.findClass(name) is called.
+ * The delegate will query the system class loader, bundle imports, bundle
+ * local classes, bundle hosts and fragments. The delegate will call
+ * BundleClassLoader.findLocalClass(name) to find a class local to this
+ * bundle.
+ * @param name the name of the class to load.
+ * @param resolve indicates whether to resolve the loaded class or not.
+ * @return The Class object.
+ * @throws ClassNotFoundException if the class is not found.
+ */
+ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ if (Debug.DEBUG_LOADER)
+ Debug.println("BundleClassLoader[" + delegate + "].loadClass(" + name + ")"); //$NON-NLS-1$ //$NON-NLS-2$//$NON-NLS-3$
+ try {
+ // Just ask the delegate. This could result in findLocalClass(name) being called.
+ Class<?> clazz = delegate.findClass(name);
+ // resolve the class if asked to.
+ if (resolve)
+ resolveClass(clazz);
+ return (clazz);
+ } catch (Error e) {
+ if (Debug.DEBUG_LOADER) {
+ Debug.println("BundleClassLoader[" + delegate + "].loadClass(" + name + ") failed."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ Debug.printStackTrace(e);
+ }
+ throw e;
+ } catch (ClassNotFoundException e) {
+ // If the class is not found do not try to look for it locally.
+ // The delegate would have already done that for us.
+ if (Debug.DEBUG_LOADER) {
+ Debug.println("BundleClassLoader[" + delegate + "].loadClass(" + name + ") failed."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ Debug.printStackTrace(e);
+ }
+ throw e;
+ }
+ }
+
+ /**
+ * Gets a resource for the bundle. First delegate.findResource(name) is
+ * called. The delegate will query the system class loader, bundle imports,
+ * bundle local resources, bundle hosts and fragments. The delegate will
+ * call BundleClassLoader.findLocalResource(name) to find a resource local
+ * to this bundle.
+ * @param name The resource path to get.
+ * @return The URL of the resource or null if it does not exist.
+ */
+ public URL getResource(String name) {
+ if (Debug.DEBUG_LOADER) {
+ Debug.println("BundleClassLoader[" + delegate + "].getResource(" + name + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ URL url = delegate.findResource(name);
+ if (url != null)
+ return (url);
+
+ if (Debug.DEBUG_LOADER) {
+ Debug.println("BundleClassLoader[" + delegate + "].getResource(" + name + ") failed."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ return (null);
+ }
+
+ /**
+ * Finds all resources with the specified name. This method must call
+ * delegate.findResources(name) to find all the resources.
+ * @param name The resource path to find.
+ * @return An Enumeration of all resources found or null if the resource.
+ * @throws IOException
+ */
+ protected Enumeration<URL> findResources(String name) throws IOException {
+ Enumeration<URL> result = delegate.findResources(name);
+ if (result == null)
+ return EMPTY_ENUMERATION;
+ return result;
+ }
+
+ /**
+ * Finds a library for this bundle. Simply calls
+ * manager.findLibrary(libname) to find the library.
+ * @param libname The library to find.
+ * @return The absolution path to the library or null if not found
+ */
+ protected String findLibrary(String libname) {
+ // let the manager find the library for us
+ return manager.findLibrary(libname);
+ }
+
+ public ProtectionDomain getDomain() {
+ return domain;
+ }
+
+ public ClasspathEntry createClassPathEntry(BundleFile bundlefile, ProtectionDomain cpDomain) {
+ return new ClasspathEntry(bundlefile, createProtectionDomain(bundlefile, cpDomain));
+ }
+
+ public Class<?> defineClass(String name, byte[] classbytes, ClasspathEntry classpathEntry, BundleEntry entry) {
+ return defineClass(name, classbytes, 0, classbytes.length, classpathEntry.getDomain());
+ }
+
+ public Class<?> publicFindLoaded(String classname) {
+ return findLoadedClass(classname);
+ }
+
+ public Object publicGetPackage(String pkgname) {
+ return getPackage(pkgname);
+ }
+
+ public Object publicDefinePackage(String name, String specTitle, String specVersion, String specVendor, String implTitle, String implVersion, String implVendor, URL sealBase) {
+ return definePackage(name, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor, sealBase);
+ }
+
+ public void initialize() {
+ manager.initialize();
+ }
+
+ public URL findLocalResource(String resource) {
+ return manager.findLocalResource(resource);
+ }
+
+ public Enumeration<URL> findLocalResources(String resource) {
+ return manager.findLocalResources(resource);
+ }
+
+ public Class<?> findLocalClass(String classname) throws ClassNotFoundException {
+ return manager.findLocalClass(classname);
+ }
+
+ public void close() {
+ manager.close();
+ }
+
+ public void attachFragment(BundleData sourcedata, ProtectionDomain sourcedomain, String[] sourceclasspath) {
+ manager.attachFragment(sourcedata, sourcedomain, sourceclasspath);
+ }
+
+ public ClassLoaderDelegate getDelegate() {
+ return delegate;
+ }
+
+ /**
+ * Creates a ProtectionDomain which uses specified BundleFile and the permissions of the baseDomain
+ * @param bundlefile The source bundlefile the domain is for.
+ * @param baseDomain The source domain.
+ * @return a ProtectionDomain which uses specified BundleFile and the permissions of the baseDomain
+ */
+ @SuppressWarnings("deprecation")
+ public static ProtectionDomain createProtectionDomain(BundleFile bundlefile, ProtectionDomain baseDomain) {
+ // create a protection domain which knows about the codesource for this classpath entry (bug 89904)
+ try {
+ // use the permissions supplied by the domain passed in from the framework
+ PermissionCollection permissions;
+ if (baseDomain != null)
+ permissions = baseDomain.getPermissions();
+ else
+ // no domain specified. Better use a collection that has all permissions
+ // this is done just incase someone sets the security manager later
+ permissions = ALLPERMISSIONS;
+ Certificate[] certs = null;
+ SignedContent signedContent = null;
+ if (bundlefile instanceof BundleFileWrapperChain) {
+ BundleFileWrapperChain wrapper = (BundleFileWrapperChain) bundlefile;
+ while (wrapper != null && (!(wrapper.getWrapped() instanceof SignedContent)))
+ wrapper = wrapper.getNext();
+ signedContent = wrapper == null ? null : (SignedContent) wrapper.getWrapped();
+ }
+ if (CLASS_CERTIFICATE && signedContent != null && signedContent.isSigned()) {
+ SignerInfo[] signers = signedContent.getSignerInfos();
+ if (signers.length > 0)
+ certs = signers[0].getCertificateChain();
+ }
+ return new BundleProtectionDomain(permissions, new CodeSource(bundlefile.getBaseFile().toURL(), certs), null);
+ } catch (MalformedURLException e) {
+ // Failed to create our own domain; just return the baseDomain
+ return baseDomain;
+ }
+ }
+
+ public ClasspathManager getClasspathManager() {
+ return manager;
+ }
+
+ public Bundle getBundle() {
+ return manager.getBaseData().getBundle();
+ }
+
+ public boolean isParallelCapable() {
+ return PARALLEL_CAPABLE;
+ }
+
+ public List<URL> findEntries(String path, String filePattern, int options) {
+ return manager.findEntries(path, filePattern, options);
+ }
+
+ public Collection<String> listResources(String path, String filePattern, int options) {
+ return delegate.listResources(path, filePattern, options);
+ }
+
+ public Collection<String> listLocalResources(String path, String filePattern, int options) {
+ return manager.listLocalResources(path, filePattern, options);
+ }
+
+ public String toString() {
+ Bundle b = getBundle();
+ StringBuffer result = new StringBuffer(super.toString());
+ if (b == null)
+ return result.toString();
+ return result.append('[').append(b.getSymbolicName()).append(':').append(b.getVersion()).append("(id=").append(b.getBundleId()).append(")]").toString(); //$NON-NLS-1$//$NON-NLS-2$
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/DevClassLoadingHook.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/DevClassLoadingHook.java
new file mode 100644
index 000000000..a3ff4dab8
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/DevClassLoadingHook.java
@@ -0,0 +1,139 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.baseadaptor;
+
+import java.io.File;
+import java.security.ProtectionDomain;
+import java.util.ArrayList;
+import org.eclipse.osgi.baseadaptor.*;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile;
+import org.eclipse.osgi.baseadaptor.hooks.ClassLoadingHook;
+import org.eclipse.osgi.baseadaptor.loader.*;
+import org.eclipse.osgi.framework.adaptor.BundleProtectionDomain;
+import org.eclipse.osgi.framework.adaptor.ClassLoaderDelegate;
+import org.eclipse.osgi.framework.util.KeyedElement;
+
+public class DevClassLoadingHook implements ClassLoadingHook, HookConfigurator, KeyedElement {
+ public static final String KEY = DevClassLoadingHook.class.getName();
+ public static final int HASHCODE = KEY.hashCode();
+ private static final String FRAGMENT = "@fragment@"; //$NON-NLS-1$
+
+ public byte[] processClass(String name, byte[] classbytes, ClasspathEntry classpathEntry, BundleEntry entry, ClasspathManager manager) {
+ // Do nothing
+ return null;
+ }
+
+ public boolean addClassPathEntry(ArrayList<ClasspathEntry> cpEntries, String cp, ClasspathManager hostmanager, BaseData sourcedata, ProtectionDomain sourcedomain) {
+ // first check that we are in devmode for this sourcedata
+ String[] devClassPath = !DevClassPathHelper.inDevelopmentMode() ? null : DevClassPathHelper.getDevClassPath(sourcedata.getSymbolicName());
+ if (devClassPath == null || devClassPath.length == 0)
+ return false; // not in dev mode return
+ // check that dev classpath entries have not already been added; we mark this in the first entry below
+ if (cpEntries.size() > 0 && cpEntries.get(0).getUserObject(KEY) != null)
+ return false; // this source has already had its dev classpath entries added.
+ boolean result = false;
+ for (int i = 0; i < devClassPath.length; i++) {
+ if (ClasspathManager.addClassPathEntry(cpEntries, devClassPath[i], hostmanager, sourcedata, sourcedomain))
+ result = true;
+ else {
+ String devCP = devClassPath[i];
+ boolean fromFragment = devCP.endsWith(FRAGMENT);
+ if (!fromFragment && devCP.indexOf("..") >= 0) { //$NON-NLS-1$
+ // if in dev mode, try using cp as a relative path from the base bundle file
+ File base = sourcedata.getBundleFile().getBaseFile();
+ if (base.isDirectory()) {
+ // this is only supported for directory bundles
+ ClasspathEntry entry = hostmanager.getExternalClassPath(new File(base, devCP).getAbsolutePath(), sourcedata, sourcedomain);
+ if (entry != null) {
+ cpEntries.add(entry);
+ result = true;
+ }
+ }
+ } else {
+ // if in dev mode, try using the cp as an absolute path
+ // we assume absolute entries come from fragments. Find the source
+ if (fromFragment)
+ devCP = devCP.substring(0, devCP.length() - FRAGMENT.length());
+ BaseData fragData = findFragmentSource(sourcedata, devCP, hostmanager, fromFragment);
+ if (fragData != null) {
+ ClasspathEntry entry = hostmanager.getExternalClassPath(devCP, fragData, sourcedomain);
+ if (entry != null) {
+ cpEntries.add(entry);
+ result = true;
+ }
+ }
+ }
+ }
+ }
+ // mark the first entry of the list.
+ // This way we can quickly tell that dev classpath entries have been added to the list
+ if (result && cpEntries.size() > 0)
+ cpEntries.get(0).addUserObject(this);
+ return result;
+ }
+
+ private BaseData findFragmentSource(BaseData hostData, String cp, ClasspathManager manager, boolean fromFragment) {
+ if (hostData != manager.getBaseData())
+ return hostData;
+
+ File file = new File(cp);
+ if (!file.isAbsolute())
+ return hostData;
+ FragmentClasspath[] fragCP = manager.getFragmentClasspaths();
+ for (int i = 0; i < fragCP.length; i++) {
+ BundleFile fragBase = fragCP[i].getBundleData().getBundleFile();
+ File fragFile = fragBase.getBaseFile();
+ if (fragFile != null && file.getPath().startsWith(fragFile.getPath()))
+ return fragCP[i].getBundleData();
+ }
+ return fromFragment ? null : hostData;
+ }
+
+ public String findLibrary(BaseData data, String libName) {
+ // Do nothing
+ return null;
+ }
+
+ public ClassLoader getBundleClassLoaderParent() {
+ // Do nothing
+ return null;
+ }
+
+ public BaseClassLoader createClassLoader(ClassLoader parent, ClassLoaderDelegate delegate, BundleProtectionDomain domain, BaseData data, String[] bundleclasspath) {
+ // do nothing
+ return null;
+ }
+
+ public void initializedClassLoader(BaseClassLoader baseClassLoader, BaseData data) {
+ // do nothing
+ }
+
+ public void addHooks(HookRegistry hookRegistry) {
+ if (DevClassPathHelper.inDevelopmentMode())
+ // only add dev classpath manager if in dev mode
+ hookRegistry.addClassLoadingHook(new DevClassLoadingHook());
+
+ }
+
+ public boolean compare(KeyedElement other) {
+ return other.getKey() == KEY;
+ }
+
+ public Object getKey() {
+ return KEY;
+ }
+
+ public int getKeyHashCode() {
+ return HASHCODE;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/DevClassPathHelper.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/DevClassPathHelper.java
new file mode 100644
index 000000000..274c07666
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/DevClassPathHelper.java
@@ -0,0 +1,160 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.baseadaptor;
+
+import java.io.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Dictionary;
+import java.util.Properties;
+import org.eclipse.osgi.framework.internal.core.FrameworkProperties;
+import org.eclipse.osgi.util.ManifestElement;
+
+/**
+ * This class provides helper methods to support developement classpaths.
+ * @since 3.1
+ */
+public final class DevClassPathHelper {
+ static final private String FILE_PROTOCOL = "file"; //$NON-NLS-1$
+ static final private boolean inDevelopmentMode;
+ static final private File devLocation;
+ static private String[] devDefaultClasspath;
+ static private Dictionary<String, String> devProperties = null;
+ // timestamp for the dev.properties file
+ static private long lastModified = 0;
+
+ static {
+ String osgiDev = FrameworkProperties.getProperty("osgi.dev"); //$NON-NLS-1$
+ File f = null;
+ boolean devMode = false;
+ if (osgiDev != null) {
+ try {
+ devMode = true;
+ URL location = new URL(osgiDev);
+
+ if (FILE_PROTOCOL.equals(location.getProtocol())) {
+ f = new File(location.getFile());
+ lastModified = f.lastModified();
+ }
+
+ // Check the osgi.dev property to see if dev classpath entries have been defined.
+ try {
+ load(location.openStream());
+ devMode = true;
+ } catch (IOException e) {
+ // TODO consider logging
+ }
+
+ } catch (MalformedURLException e) {
+ devDefaultClasspath = getArrayFromList(osgiDev);
+ }
+ }
+ inDevelopmentMode = devMode;
+ devLocation = f;
+ }
+
+ /*
+ * Updates the dev classpath if the file containing the entries have changed
+ */
+ private static void updateDevProperties() {
+ if (devLocation == null)
+ return;
+ if (devLocation.lastModified() == lastModified)
+ return;
+
+ try {
+ load(new FileInputStream(devLocation));
+ } catch (FileNotFoundException e) {
+ return;
+ }
+ lastModified = devLocation.lastModified();
+ }
+
+ private static String[] getDevClassPath(String id, Dictionary<String, String> properties, String[] defaultClasspath) {
+ String[] result = null;
+ if (id != null && properties != null) {
+ String entry = properties.get(id);
+ if (entry != null)
+ result = getArrayFromList(entry);
+ }
+ if (result == null)
+ result = defaultClasspath;
+ return result;
+ }
+
+ /**
+ * Returns a list of classpath elements for the specified bundle symbolic name.
+ * @param id a bundle symbolic name to get the development classpath for
+ * @param properties a Dictionary of properties to use or <code>null</code> if
+ * the default develoment classpath properties should be used
+ * @return a list of development classpath elements
+ */
+ public static String[] getDevClassPath(String id, Dictionary<String, String> properties) {
+ if (properties == null) {
+ synchronized (DevClassPathHelper.class) {
+ updateDevProperties();
+ return getDevClassPath(id, devProperties, devDefaultClasspath);
+ }
+ }
+ return getDevClassPath(id, properties, getArrayFromList(properties.get("*"))); //$NON-NLS-1$
+ }
+
+ /**
+ * Returns a list of classpath elements for the specified bundle symbolic name.
+ * @param id a bundle symbolic name to get the development classpath for
+ * @return a list of development classpath elements
+ */
+ public static String[] getDevClassPath(String id) {
+ return getDevClassPath(id, null);
+ }
+
+ /**
+ * Returns the result of converting a list of comma-separated tokens into an array
+ *
+ * @return the array of string tokens
+ * @param prop the initial comma-separated string
+ */
+ public static String[] getArrayFromList(String prop) {
+ return ManifestElement.getArrayFromList(prop, ","); //$NON-NLS-1$
+ }
+
+ /**
+ * Indicates the development mode.
+ * @return true if in development mode; false otherwise
+ */
+ public static boolean inDevelopmentMode() {
+ return inDevelopmentMode;
+ }
+
+ /*
+ * Load the given input stream into a dictionary
+ */
+ private static void load(InputStream input) {
+ Properties props = new Properties();
+ try {
+ props.load(input);
+ } catch (IOException e) {
+ // TODO consider logging here
+ } finally {
+ if (input != null)
+ try {
+ input.close();
+ } catch (IOException e) {
+ // tried our best
+ }
+ }
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ Dictionary<String, String> result = (Dictionary) props;
+ devProperties = result;
+ if (devProperties != null)
+ devDefaultClasspath = getArrayFromList(devProperties.get("*")); //$NON-NLS-1$
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/ExternalMessages.properties b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/ExternalMessages.properties
new file mode 100644
index 000000000..13f045f2f
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/ExternalMessages.properties
@@ -0,0 +1,35 @@
+###############################################################################
+# Copyright (c) 2003, 2011 IBM Corporation and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Contributors:
+# IBM Corporation - initial API and implementation
+###############################################################################
+
+#External Messages for EN locale
+
+ADAPTOR_STORAGE_EXCEPTION=The FrameworkAdaptor object could not perform the operation
+ADAPTOR_STORAGE_INIT_FAILED_MSG=Locking is not possible in the directory \"{0}\". A common reason is that the file system or Runtime Environment does not support file locking for that location. Please choose a different location, or disable file locking passing \"-Dosgi.locking=none\" as a VM argument.
+ADAPTOR_STORAGE_INIT_FAILED_TITLE =Invalid Configuration Location
+ADAPTOR_URL_CREATE_EXCEPTION=\"{0}\" is an invalid URL
+ADAPTOR_DIRECTORY_CREATE_EXCEPTION=The directory \"{0}\" could not be created
+ADAPTOR_DIRECTORY_EXCEPTION=The file \"{0}\" is not a directory
+ADAPTER_FILEEXIST_EXCEPTION=The file \"{0}\" does not exist
+ADAPTOR_EXTENSION_NATIVECODE_ERROR=Extension bundle fragments cannot specify a Bundle-NativeCode header: {0}
+ADAPTOR_EXTENSION_REQUIRE_ERROR=Extension bundle fragments cannot specify a Require-Bundle header: {0}
+ADAPTOR_EXTENSION_IMPORT_ERROR=Extension bundle fragments cannot specify an Import-Package header: {0}
+
+SYSTEMBUNDLE_MISSING_MANIFEST = Unable to find system bundle manifest file.
+SYSTEMBUNDLE_NOTRESOLVED = The System Bundle could not be resolved: {0}
+BUNDLE_READ_EXCEPTION=An error occurred trying to read the bundle
+BUNDLE_NATIVECODE_EXCEPTION=The Bundle-NativeCode file {0} could not be found
+BUNDLE_CLASSPATH_ENTRY_NOT_FOUND_EXCEPTION=The bundle class path entry \"{0}\" could not be found for the bundle \"{1}\"
+MANIFEST_NOT_FOUND_EXCEPTION=The manifest file {0} could not be found in the bundle {1}
+
+RESOURCE_NOT_FOUND_EXCEPTION=The resource {0} could not be found
+URL_NO_BUNDLE_ID=No bundle ID specified: {0}
+URL_INVALID_BUNDLE_ID=Invalid Bundle ID specified: {0}
+URL_NO_BUNDLE_FOUND=No bundle found for URL: {0}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/InvalidVersion.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/InvalidVersion.java
new file mode 100644
index 000000000..1dea537a7
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/InvalidVersion.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.baseadaptor;
+
+import org.osgi.framework.Version;
+
+/**
+ * This class is used to hold invalid version strings. This is used to support
+ * OSGi R3 bundles which could have an invalid Bundle-Version header. An
+ * InvalidVersion always has a value of 0.0.0.
+ */
+public class InvalidVersion extends Version {
+ private String invalidVersion;
+
+ /**
+ * Constructs a BadVersion using the specified invalid version string.
+ * @param badVersion an invalid version string.
+ */
+ public InvalidVersion(String badVersion) {
+ super(0, 0, 0, null);
+ this.invalidVersion = badVersion;
+ }
+
+ /**
+ * Returns the invalid version string.
+ * @return the invalid version string.
+ */
+ public String getInvalidVersion() {
+ return invalidVersion;
+ }
+
+ public String toString() {
+ return invalidVersion;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/StateManager.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/StateManager.java
new file mode 100644
index 000000000..167385a95
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/StateManager.java
@@ -0,0 +1,320 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Rob Harrop - SpringSource Inc. (bug 247522)
+ *******************************************************************************/
+package org.eclipse.osgi.internal.baseadaptor;
+
+import java.io.File;
+import java.io.IOException;
+import org.eclipse.osgi.framework.internal.core.FrameworkProperties;
+import org.eclipse.osgi.internal.resolver.*;
+import org.eclipse.osgi.service.resolver.*;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+
+/**
+ * The StateManager manages the system state for the framework. It also provides the implementation
+ * to the PlatformAdmin service.
+ * <p>
+ * Clients may extend this class.
+ * </p>
+ * @since 3.1
+ */
+public class StateManager implements PlatformAdmin, Runnable {
+ /**
+ * General debug flag
+ */
+ public static boolean DEBUG = false;
+ /**
+ * Reader debug flag
+ */
+ public static boolean DEBUG_READER = false;
+ /**
+ * PlatformAdmin debug flag
+ */
+ public static boolean DEBUG_PLATFORM_ADMIN = false;
+ /**
+ * PlatformAdmin resolver debug flag
+ */
+ public static boolean DEBUG_PLATFORM_ADMIN_RESOLVER = false;
+ /**
+ * Monitor PlatformAdmin debug flag
+ */
+ public static boolean MONITOR_PLATFORM_ADMIN = false;
+ /**
+ * System property used to disable lazy state loading
+ */
+ public static String PROP_NO_LAZY_LOADING = "osgi.noLazyStateLoading"; //$NON-NLS-1$
+ /**
+ * System property used to specify to amount time before lazy data can be flushed from memory
+ */
+ public static String PROP_LAZY_UNLOADING_TIME = "osgi.lazyStateUnloadingTime"; //$NON-NLS-1$
+ private long expireTime = 300000; // default to five minutes
+ private long readStartupTime;
+ private StateImpl systemState;
+ private final StateObjectFactoryImpl factory;
+ private long lastTimeStamp;
+ private boolean cachedState = false;
+ private final File stateFile;
+ private final File lazyFile;
+ private final long expectedTimeStamp;
+ private final BundleContext context;
+ private Thread dataManagerThread;
+
+ /**
+ * Constructs a StateManager using the specified files and context
+ * @param stateFile a file with the data required to persist in memory
+ * @param lazyFile a file with the data that may be lazy loaded and can be flushed from memory
+ * @param context the bundle context of the system bundle
+ */
+ public StateManager(File stateFile, File lazyFile, BundleContext context) {
+ // a negative timestamp means no timestamp checking
+ this(stateFile, lazyFile, context, -1);
+ }
+
+ /**
+ * Constructs a StateManager using the specified files and context
+ * @param stateFile a file with the data required to persist in memory
+ * @param lazyFile a file with the data that may be lazy loaded and can be flushed from memory
+ * @param context the bundle context of the system bundle
+ * @param expectedTimeStamp the expected timestamp of the persisted system state. A negative
+ * value indicates that no timestamp checking is done
+ */
+ public StateManager(File stateFile, File lazyFile, BundleContext context, long expectedTimeStamp) {
+ this.stateFile = stateFile;
+ this.lazyFile = lazyFile;
+ this.context = context;
+ this.expectedTimeStamp = expectedTimeStamp;
+ factory = new StateObjectFactoryImpl();
+ }
+
+ /**
+ * Shutsdown the state manager. If the timestamp of the system state has changed
+ * @param saveStateFile
+ * @param saveLazyFile
+ * @throws IOException
+ */
+ public void shutdown(File saveStateFile, File saveLazyFile) throws IOException {
+ writeState(systemState, saveStateFile, saveLazyFile);
+ stopDataManager();
+ }
+
+ /**
+ * Update the given target files with the state data in memory.
+ * @param updateStateFile
+ * @param updateLazyFile
+ * @throws IOException
+ */
+ public void update(File updateStateFile, File updateLazyFile) throws IOException {
+ writeState(systemState, updateStateFile, updateLazyFile);
+ // Need to use the timestamp of the original state here
+ lastTimeStamp = systemState.getTimeStamp();
+ // TODO consider updating the state files for lazy loading
+ }
+
+ private void internalReadSystemState() {
+ if (stateFile == null || !stateFile.isFile())
+ return;
+ if (DEBUG_READER)
+ readStartupTime = System.currentTimeMillis();
+ try {
+ boolean lazyLoad = !Boolean.valueOf(FrameworkProperties.getProperty(PROP_NO_LAZY_LOADING)).booleanValue();
+ systemState = factory.readSystemState(context, stateFile, lazyFile, lazyLoad, expectedTimeStamp);
+ // problems in the cache (corrupted/stale), don't create a state object
+ if (systemState == null || !initializeSystemState()) {
+ systemState = null;
+ return;
+ }
+ cachedState = true;
+ try {
+ expireTime = Long.parseLong(FrameworkProperties.getProperty(PROP_LAZY_UNLOADING_TIME, Long.toString(expireTime)));
+ } catch (NumberFormatException nfe) {
+ // default to not expire
+ expireTime = 0;
+ }
+ if (lazyLoad && expireTime > 0)
+ startDataManager();
+ } catch (IOException ioe) {
+ // TODO: how do we log this?
+ ioe.printStackTrace();
+ } finally {
+ if (DEBUG_READER)
+ System.out.println("Time to read state: " + (System.currentTimeMillis() - readStartupTime)); //$NON-NLS-1$
+ }
+ }
+
+ private synchronized void startDataManager() {
+ if (dataManagerThread != null)
+ return;
+ dataManagerThread = new Thread(this, "State Data Manager"); //$NON-NLS-1$
+ dataManagerThread.setDaemon(true);
+ dataManagerThread.start();
+ }
+
+ /**
+ * Stops the active data manager thread which is used to unload unused
+ * state objects from memory.
+ */
+ public synchronized void stopDataManager() {
+ if (dataManagerThread == null)
+ return;
+ dataManagerThread.interrupt();
+ dataManagerThread = null;
+ }
+
+ private void writeState(StateImpl state, File saveStateFile, File saveLazyFile) throws IOException {
+ if (state == null)
+ return;
+ if (cachedState && !saveNeeded())
+ return;
+ state.fullyLoad(); // make sure we are fully loaded before saving
+ factory.writeState(state, saveStateFile, saveLazyFile);
+ }
+
+ private boolean initializeSystemState() {
+ systemState.setResolver(createResolver(System.getSecurityManager() != null));
+ lastTimeStamp = systemState.getTimeStamp();
+ return !systemState.setPlatformProperties(FrameworkProperties.getProperties());
+ }
+
+ /**
+ * Creates a new State used by the system. If the system State already
+ * exists then a new system State is not created.
+ * @return the State used by the system.
+ */
+ public synchronized State createSystemState() {
+ if (systemState == null) {
+ systemState = factory.createSystemState(context);
+ initializeSystemState();
+ }
+ return systemState;
+ }
+
+ /**
+ * Reads the State used by the system. If the system State already
+ * exists then the system State is not read from a cache. If the State could
+ * not be read from a cache then <code>null</code> is returned.
+ * @return the State used by the system or <code>null</code> if the State
+ * could not be read from a cache.
+ */
+ public synchronized State readSystemState() {
+ if (systemState == null)
+ internalReadSystemState();
+ return systemState;
+ }
+
+ /**
+ * Returns the State used by the system. If the system State does
+ * not exist then <code>null</code> is returned.
+ * @return the State used by the system or <code>null</code> if one
+ * does not exist.
+ */
+ public State getSystemState() {
+ return systemState;
+ }
+
+ /**
+ * Returns the cached time stamp of the system State. This value is the
+ * original time stamp of the system state when it was created or read from
+ * a cache.
+ * @see State#getTimeStamp()
+ * @return the cached time stamp of the system State
+ */
+ public long getCachedTimeStamp() {
+ return lastTimeStamp;
+ }
+
+ public boolean saveNeeded() {
+ return systemState.getTimeStamp() != lastTimeStamp || systemState.dynamicCacheChanged();
+ }
+
+ /**
+ * @see PlatformAdmin#getState(boolean)
+ */
+ public State getState(boolean mutable) {
+ return mutable ? factory.createState(systemState) : new ReadOnlyState(systemState);
+ }
+
+ /**
+ * @see PlatformAdmin#getState()
+ */
+ public State getState() {
+ return getState(true);
+ }
+
+ /**
+ * @see PlatformAdmin#getFactory()
+ */
+ public StateObjectFactory getFactory() {
+ return factory;
+ }
+
+ /**
+ * @throws BundleException
+ * @see PlatformAdmin#commit(State)
+ */
+ public synchronized void commit(State state) throws BundleException {
+ throw new IllegalArgumentException("PlatformAdmin.commit() not supported"); //$NON-NLS-1$
+ }
+
+ /**
+ * @see PlatformAdmin#getResolver()
+ * @deprecated
+ */
+ public Resolver getResolver() {
+ return createResolver(false);
+ }
+
+ /**
+ * @see PlatformAdmin#createResolver()
+ */
+ public Resolver createResolver() {
+ return createResolver(false);
+ }
+
+ private Resolver createResolver(boolean checkPermissions) {
+ return new org.eclipse.osgi.internal.module.ResolverImpl(checkPermissions);
+ }
+
+ /**
+ * @see PlatformAdmin#getStateHelper()
+ */
+ public StateHelper getStateHelper() {
+ return StateHelperImpl.getInstance();
+ }
+
+ public void run() {
+ long timeStamp = lastTimeStamp; // cache the original timestamp incase of updates
+ while (true) {
+ try {
+ Thread.sleep(expireTime);
+ } catch (InterruptedException e) {
+ return;
+ }
+ if (systemState != null)
+ synchronized (systemState) {
+ if (!systemState.unloadLazyData(timeStamp))
+ return;
+ }
+ }
+ }
+
+ public void addDisabledInfo(DisabledInfo disabledInfo) {
+ if (systemState == null)
+ throw new IllegalStateException(); // should never happen
+ systemState.addDisabledInfo(disabledInfo);
+ }
+
+ public void removeDisabledInfo(DisabledInfo disabledInfo) {
+ if (systemState == null)
+ throw new IllegalStateException(); // should never happen
+ systemState.removeDisabledInfo(disabledInfo);
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/SystemBundleData.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/SystemBundleData.java
new file mode 100644
index 000000000..9604b59ed
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/SystemBundleData.java
@@ -0,0 +1,219 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.baseadaptor;
+
+import java.io.*;
+import java.net.URL;
+import java.util.Enumeration;
+import org.eclipse.osgi.baseadaptor.BaseAdaptor;
+import org.eclipse.osgi.baseadaptor.BaseData;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile;
+import org.eclipse.osgi.baseadaptor.hooks.StorageHook;
+import org.eclipse.osgi.framework.adaptor.*;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.framework.internal.core.Constants;
+import org.eclipse.osgi.framework.internal.core.FrameworkProperties;
+import org.eclipse.osgi.framework.util.Headers;
+import org.osgi.framework.BundleException;
+
+public class SystemBundleData extends BaseData {
+ private static final String OSGI_FRAMEWORK = "osgi.framework"; //$NON-NLS-1$
+
+ public SystemBundleData(BaseAdaptor adaptor) throws BundleException {
+ super(0, adaptor);
+ File osgiBase = getOsgiBase();
+ createBundleFile(osgiBase);
+ manifest = createManifest(osgiBase);
+ setMetaData();
+ setLastModified(System.currentTimeMillis()); // just set the lastModified to the current time
+ StorageHook[] hooks = adaptor.getHookRegistry().getStorageHooks();
+ StorageHook[] instanceHooks = new StorageHook[hooks.length];
+ for (int i = 0; i < hooks.length; i++)
+ instanceHooks[i] = hooks[i].create(this);
+ setStorageHooks(instanceHooks);
+ }
+
+ private File getOsgiBase() {
+ String frameworkLocation = FrameworkProperties.getProperty(SystemBundleData.OSGI_FRAMEWORK);
+ if (frameworkLocation != null && frameworkLocation.startsWith("file:")) //$NON-NLS-1$
+ // TODO assumes the location is a file URL
+ return new File(frameworkLocation.substring(5));
+ try {
+ URL url = getClass().getProtectionDomain().getCodeSource().getLocation();
+ // assumes file URL
+ if ("file".equals(url.getProtocol())) //$NON-NLS-1$
+ return new File(url.getPath());
+ } catch (Throwable e) {
+ // do nothing
+ }
+ return null;
+ }
+
+ private Headers<String, String> createManifest(File osgiBase) throws BundleException {
+ InputStream in = null;
+
+ if (osgiBase != null && osgiBase.exists())
+ try {
+ BundleEntry entry = getBundleFile().getEntry(Constants.OSGI_BUNDLE_MANIFEST);
+ if (entry != null)
+ in = entry.getInputStream();
+ } catch (IOException e) {
+ // do nothing here. in == null
+ }
+
+ // If we cannot find the Manifest file from the baseBundleFile then
+ // search for the manifest as a classloader resource
+ // This allows an adaptor to package the SYSTEMBUNDLE.MF file in a jar.
+ if (in == null)
+ in = getManifestAsResource();
+ if (Debug.DEBUG_GENERAL)
+ if (in == null)
+ Debug.println("Unable to find system bundle manifest " + Constants.OSGI_BUNDLE_MANIFEST); //$NON-NLS-1$
+
+ if (in == null)
+ throw new BundleException(AdaptorMsg.SYSTEMBUNDLE_MISSING_MANIFEST, BundleException.MANIFEST_ERROR);
+ return Headers.parseManifest(in);
+ }
+
+ private InputStream getManifestAsResource() {
+ URL url = getManifestURL();
+ try {
+ return url == null ? null : url.openStream();
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ private URL getManifestURL() {
+ ClassLoader cl = getClass().getClassLoader();
+ try {
+ // get all manifests in your classloader delegation
+ Enumeration<URL> manifests = cl != null ? cl.getResources(Constants.OSGI_BUNDLE_MANIFEST) : ClassLoader.getSystemResources(Constants.OSGI_BUNDLE_MANIFEST);
+ while (manifests.hasMoreElements()) {
+ URL url = manifests.nextElement();
+ try {
+ // check each manifest until we find one with the Eclipse-SystemBundle: true header
+ Headers<String, String> headers = Headers.parseManifest(url.openStream());
+ if ("true".equals(headers.get(Constants.ECLIPSE_SYSTEMBUNDLE))) //$NON-NLS-1$
+ return url;
+ } catch (BundleException e) {
+ // ignore and continue to next URL
+ }
+ }
+ } catch (IOException e) {
+ // ignore and return null
+ }
+ return null;
+ }
+
+ private void createBundleFile(File osgiBase) {
+ if (osgiBase != null)
+ try {
+ bundleFile = getAdaptor().createBundleFile(osgiBase, this);
+ } catch (IOException e) {
+ // should not happen
+ }
+ else
+ bundleFile = new BundleFile(osgiBase) {
+ public File getFile(String path, boolean nativeCode) {
+ return null;
+ }
+
+ public BundleEntry getEntry(String path) {
+ if (Constants.OSGI_BUNDLE_MANIFEST.equals(path)) {
+ System.err.println("Getting system bundle manifest: " + path);
+ return new BundleEntry() {
+
+ public InputStream getInputStream() throws IOException {
+ return getManifestURL().openStream();
+ }
+
+ public long getSize() {
+ return 0;
+ }
+
+ public String getName() {
+ return Constants.OSGI_BUNDLE_MANIFEST;
+ }
+
+ public long getTime() {
+ return 0;
+ }
+
+ public URL getLocalURL() {
+ return getManifestURL();
+ }
+
+ public URL getFileURL() {
+ return null;
+ }
+ };
+ }
+ return null;
+ }
+
+ public Enumeration<String> getEntryPaths(String path) {
+ return null;
+ }
+
+ public void close() {
+ // do nothing
+ }
+
+ public void open() {
+ // do nothing
+ }
+
+ public boolean containsDir(String dir) {
+ return false;
+ }
+ };
+ }
+
+ private void setMetaData() throws BundleException {
+ setLocation(Constants.SYSTEM_BUNDLE_LOCATION);
+ BaseStorageHook.loadManifest(this, manifest);
+ }
+
+ public BundleClassLoader createClassLoader(ClassLoaderDelegate delegate, BundleProtectionDomain domain, String[] bundleclasspath) {
+ return null;
+ }
+
+ public File createGenerationDir() {
+ return null;
+ }
+
+ public String findLibrary(String libname) {
+ return null;
+ }
+
+ /**
+ * @throws BundleException
+ */
+ public void installNativeCode(String[] nativepaths) throws BundleException {
+ // do nothing
+ }
+
+ public int getStartLevel() {
+ return 0;
+ }
+
+ public int getStatus() {
+ return Constants.BUNDLE_STARTED;
+ }
+
+ public void save() {
+ // do nothing
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/weaving/DynamicImportList.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/weaving/DynamicImportList.java
new file mode 100644
index 000000000..789beae53
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/weaving/DynamicImportList.java
@@ -0,0 +1,83 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.baseadaptor.weaving;
+
+import java.util.*;
+import org.eclipse.osgi.internal.resolver.StateBuilder;
+import org.eclipse.osgi.service.resolver.ImportPackageSpecification;
+import org.eclipse.osgi.util.ManifestElement;
+import org.osgi.framework.Constants;
+
+/**
+ * A list of DynamicImport-Package statements that are to be used for adding new
+ * dynamic imports to a bundle class loader.
+ *
+ */
+public class DynamicImportList extends AbstractList<String> implements RandomAccess {
+ // the collection of valid DynamicImport-Package statments.
+ private final List<String> imports = new ArrayList<String>(0);
+ private final WovenClassImpl wovenClass;
+
+ public DynamicImportList(WovenClassImpl wovenClass) {
+ super();
+ this.wovenClass = wovenClass;
+ }
+
+ @Override
+ public String get(int index) {
+ return imports.get(index);
+ }
+
+ @Override
+ public int size() {
+ return imports.size();
+ }
+
+ @Override
+ public String set(int index, String element) {
+ wovenClass.checkPermission();
+ validateSyntax(element);
+ return imports.set(index, element);
+ }
+
+ @Override
+ public void add(int index, String element) {
+ wovenClass.checkPermission();
+ validateSyntax(element);
+ imports.add(index, element);
+ }
+
+ @Override
+ public String remove(int index) {
+ wovenClass.checkPermission();
+ return imports.remove(index);
+ }
+
+ private void validateSyntax(String imported) {
+ // validate the syntax of imports that are added.
+ ManifestElement[] importElements;
+ try {
+ importElements = ManifestElement.parseHeader(Constants.IMPORT_PACKAGE, imported);
+
+ // validate the syntax is correct
+ StateBuilder.checkImportExportSyntax(Constants.IMPORT_PACKAGE, importElements, false, false, false);
+ // validate we can create an import spec out of it.
+ List<ImportPackageSpecification> dynamicImportSpecs = new ArrayList<ImportPackageSpecification>(importElements.length);
+ for (ManifestElement dynamicImportElement : importElements)
+ StateBuilder.addImportPackages(dynamicImportElement, dynamicImportSpecs, 2, true);
+ } catch (Throwable t) {
+ IllegalArgumentException exception = new IllegalArgumentException();
+ exception.initCause(t);
+ throw exception;
+ }
+ return;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/weaving/WeavingHookConfigurator.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/weaving/WeavingHookConfigurator.java
new file mode 100644
index 000000000..9f43d0ec4
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/weaving/WeavingHookConfigurator.java
@@ -0,0 +1,126 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.baseadaptor.weaving;
+
+import java.net.URL;
+import java.security.ProtectionDomain;
+import java.util.*;
+import org.eclipse.osgi.baseadaptor.*;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
+import org.eclipse.osgi.baseadaptor.hooks.ClassLoadingHook;
+import org.eclipse.osgi.baseadaptor.hooks.ClassLoadingStatsHook;
+import org.eclipse.osgi.baseadaptor.loader.*;
+import org.eclipse.osgi.framework.adaptor.BundleProtectionDomain;
+import org.eclipse.osgi.framework.adaptor.ClassLoaderDelegate;
+import org.eclipse.osgi.framework.internal.core.Framework;
+import org.eclipse.osgi.internal.loader.BundleLoader;
+import org.eclipse.osgi.internal.serviceregistry.ServiceRegistry;
+import org.osgi.framework.*;
+
+public class WeavingHookConfigurator implements HookConfigurator, ClassLoadingHook, ClassLoadingStatsHook {
+ private BaseAdaptor adaptor;
+ // holds the map of black listed hooks. Use weak map to avoid pinning and simplify cleanup.
+ private final Map<ServiceRegistration<?>, Boolean> blackList = Collections.synchronizedMap(new WeakHashMap<ServiceRegistration<?>, Boolean>());
+ // holds the stack of WovenClass objects currently being used to define classes
+ private final ThreadLocal<List<WovenClassImpl>> wovenClassStack = new ThreadLocal<List<WovenClassImpl>>();
+
+ public void addHooks(HookRegistry hookRegistry) {
+ this.adaptor = hookRegistry.getAdaptor();
+ hookRegistry.addClassLoadingHook(this);
+ hookRegistry.addClassLoadingStatsHook(this);
+ }
+
+ private ServiceRegistry getRegistry() {
+ return ((Framework) adaptor.getEventPublisher()).getServiceRegistry();
+ }
+
+ public byte[] processClass(String name, byte[] classbytes, ClasspathEntry classpathEntry, BundleEntry entry, ClasspathManager manager) {
+ ServiceRegistry registry = getRegistry();
+ if (registry == null)
+ return null; // no registry somehow we are loading classes before the registry has been created
+ ClassLoaderDelegate delegate = manager.getBaseClassLoader().getDelegate();
+ BundleLoader loader;
+ if (delegate instanceof BundleLoader) {
+ loader = (BundleLoader) delegate;
+ } else {
+ Throwable e = new IllegalStateException("Could not obtain loader"); //$NON-NLS-1$
+ adaptor.getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, manager.getBaseData().getBundle(), e);
+ return null;
+ }
+ // create a woven class object and add it to the thread local stack
+ WovenClassImpl wovenClass = new WovenClassImpl(name, classbytes, entry, classpathEntry.getDomain(), loader, registry, blackList);
+ List<WovenClassImpl> wovenClasses = wovenClassStack.get();
+ if (wovenClasses == null) {
+ wovenClasses = new ArrayList<WovenClassImpl>(6);
+ wovenClassStack.set(wovenClasses);
+ }
+ wovenClasses.add(wovenClass);
+ // call the weaving hooks
+ try {
+ return wovenClass.callHooks();
+ } catch (Throwable t) {
+ ServiceRegistration<?> errorHook = wovenClass.getErrorHook();
+ Bundle errorBundle = errorHook != null ? errorHook.getReference().getBundle() : manager.getBaseData().getBundle();
+ adaptor.getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, errorBundle, t);
+ // fail hard with a class loading error
+ ClassFormatError error = new ClassFormatError("Unexpected error from weaving hook."); //$NON-NLS-1$
+ error.initCause(t);
+ throw error;
+ }
+ }
+
+ public boolean addClassPathEntry(ArrayList<ClasspathEntry> cpEntries, String cp, ClasspathManager hostmanager, BaseData sourcedata, ProtectionDomain sourcedomain) {
+ return false;
+ }
+
+ public String findLibrary(BaseData data, String libName) {
+ return null;
+ }
+
+ public ClassLoader getBundleClassLoaderParent() {
+ return null;
+ }
+
+ public BaseClassLoader createClassLoader(ClassLoader parent, ClassLoaderDelegate delegate, BundleProtectionDomain domain, BaseData data, String[] bundleclasspath) {
+ return null;
+ }
+
+ public void initializedClassLoader(BaseClassLoader baseClassLoader, BaseData data) {
+ // nothing
+ }
+
+ public void preFindLocalClass(String name, ClasspathManager manager) {
+ // nothing
+ }
+
+ public void postFindLocalClass(String name, Class<?> clazz, ClasspathManager manager) {
+ // nothing
+ }
+
+ public void preFindLocalResource(String name, ClasspathManager manager) {
+ // nothing
+ }
+
+ public void postFindLocalResource(String name, URL resource, ClasspathManager manager) {
+ // nothing
+ }
+
+ public void recordClassDefine(String name, Class<?> clazz, byte[] classbytes, ClasspathEntry classpathEntry, BundleEntry entry, ClasspathManager manager) {
+ // here we assume the stack contans a woven class with the same name as the class we are defining.
+ List<WovenClassImpl> wovenClasses = wovenClassStack.get();
+ if (wovenClasses == null || wovenClasses.size() == 0)
+ return;
+ WovenClassImpl wovenClass = wovenClasses.remove(wovenClasses.size() - 1);
+ // inform the woven class about the class that was defined.
+ wovenClass.setWeavingCompleted(clazz);
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/weaving/WovenClassImpl.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/weaving/WovenClassImpl.java
new file mode 100644
index 000000000..e7e27a37c
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/baseadaptor/weaving/WovenClassImpl.java
@@ -0,0 +1,228 @@
+/*******************************************************************************
+ * Copyright (c) 2010, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.baseadaptor.weaving;
+
+import java.security.*;
+import java.util.*;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
+import org.eclipse.osgi.internal.baseadaptor.AdaptorUtil;
+import org.eclipse.osgi.internal.loader.BundleLoader;
+import org.eclipse.osgi.internal.serviceregistry.HookContext;
+import org.eclipse.osgi.internal.serviceregistry.ServiceRegistry;
+import org.eclipse.osgi.util.ManifestElement;
+import org.osgi.framework.*;
+import org.osgi.framework.hooks.weaving.*;
+import org.osgi.framework.wiring.BundleWiring;
+
+public final class WovenClassImpl implements WovenClass, HookContext {
+ private final static byte FLAG_HOOKCALLED = 0x01;
+ private final static byte FLAG_HOOKSCOMPLETE = 0x02;
+ private final static byte FLAG_WEAVINGCOMPLETE = 0x04;
+ private final static String weavingHookName = WeavingHook.class.getName();
+ private final String className;
+ private final BundleEntry entry;
+ private final List<String> dynamicImports;
+ private final ProtectionDomain domain;
+ private final BundleLoader loader;
+ final ServiceRegistry registry;
+ private final Map<ServiceRegistration<?>, Boolean> blackList;
+ private byte[] validBytes;
+ private byte[] resultBytes;
+ private byte hookFlags = 0;
+ private Throwable error;
+ private ServiceRegistration<?> errorHook;
+ private Class<?> clazz;
+
+ public WovenClassImpl(String className, byte[] bytes, BundleEntry entry, ProtectionDomain domain, BundleLoader loader, ServiceRegistry registry, Map<ServiceRegistration<?>, Boolean> blacklist) {
+ super();
+ this.className = className;
+ this.validBytes = this.resultBytes = bytes;
+ this.entry = entry;
+ this.dynamicImports = new DynamicImportList(this);
+ this.domain = domain;
+ this.loader = loader;
+ this.registry = registry;
+ this.blackList = blacklist;
+ }
+
+ public byte[] getBytes() {
+ if ((hookFlags & FLAG_HOOKSCOMPLETE) == 0) {
+ checkPermission();
+ return validBytes; // return raw bytes until complete
+ }
+ // we have called all hooks; someone is calling outside of weave call
+ // need to be safe and copy the bytes.
+ byte[] current = validBytes;
+ byte[] results = new byte[current.length];
+ System.arraycopy(current, 0, results, 0, current.length);
+ return results;
+ }
+
+ public void setBytes(byte[] newBytes) {
+ checkPermission();
+ if (newBytes == null)
+ throw new NullPointerException("newBytes cannot be null."); //$NON-NLS-1$
+ if ((hookFlags & FLAG_HOOKSCOMPLETE) != 0)
+ // someone is calling this outside of weave
+ throw new IllegalStateException("Weaving has completed already."); //$NON-NLS-1$
+ this.resultBytes = this.validBytes = newBytes;
+ }
+
+ void checkPermission() {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null)
+ sm.checkPermission(new AdminPermission(loader.getBundle(), AdminPermission.WEAVE));
+ }
+
+ public List<String> getDynamicImports() {
+ if ((hookFlags & FLAG_HOOKSCOMPLETE) == 0)
+ return dynamicImports;
+ // being called outside of weave; return unmodified list
+ return Collections.unmodifiableList(dynamicImports);
+ }
+
+ public boolean isWeavingComplete() {
+ return (hookFlags & FLAG_WEAVINGCOMPLETE) != 0;
+ }
+
+ private void setHooksComplete() {
+ // create a copy of the bytes array that noone has a reference to
+ byte[] original = validBytes;
+ validBytes = new byte[original.length];
+ System.arraycopy(original, 0, validBytes, 0, original.length);
+ hookFlags |= FLAG_HOOKSCOMPLETE;
+ }
+
+ void setWeavingCompleted(Class<?> clazz) {
+ // weaving has completed; save the class and mark complete
+ this.clazz = clazz;
+ hookFlags |= FLAG_WEAVINGCOMPLETE;
+ }
+
+ public String getClassName() {
+ return className;
+ }
+
+ public ProtectionDomain getProtectionDomain() {
+ return domain;
+ }
+
+ public Class<?> getDefinedClass() {
+ return clazz;
+ }
+
+ public BundleWiring getBundleWiring() {
+ return loader.getLoaderProxy().getBundleDescription().getWiring();
+ }
+
+ public void call(final Object hook, ServiceRegistration<?> hookRegistration) throws Exception {
+ if (error != null)
+ return; // do not call any other hooks once an error has occurred.
+ if (hook instanceof WeavingHook) {
+ if (blackList.containsKey(hookRegistration))
+ return; // black listed hook
+ if ((hookFlags & FLAG_HOOKCALLED) == 0) {
+ hookFlags |= FLAG_HOOKCALLED;
+ // only do this check on the first weaving hook call
+ if (!validBytes(validBytes)) {
+ validBytes = AdaptorUtil.getBytes(entry.getInputStream(), (int) entry.getSize(), 8 * 1024);
+ }
+ }
+ try {
+ ((WeavingHook) hook).weave(this);
+ } catch (WeavingException e) {
+ error = e;
+ errorHook = hookRegistration;
+ // do not blacklist on weaving exceptions
+ } catch (Throwable t) {
+ error = t; // save the error to fail later
+ errorHook = hookRegistration;
+ // put the registration on the black list
+ blackList.put(hookRegistration, Boolean.TRUE);
+ }
+ }
+ }
+
+ private boolean validBytes(byte[] checkBytes) {
+ if (checkBytes == null || checkBytes.length < 4)
+ return false;
+ if ((checkBytes[0] & 0xCA) != 0xCA)
+ return false;
+ if ((checkBytes[1] & 0xFE) != 0xFE)
+ return false;
+ if ((checkBytes[2] & 0xBA) != 0xBA)
+ return false;
+ if ((checkBytes[3] & 0xBE) != 0xBE)
+ return false;
+ return true;
+ }
+
+ public String getHookMethodName() {
+ return "weave"; //$NON-NLS-1$
+ }
+
+ public String getHookClassName() {
+ return weavingHookName;
+ }
+
+ byte[] callHooks() throws Throwable {
+ SecurityManager sm = System.getSecurityManager();
+ byte[] wovenBytes = null;
+ List<String> newImports = null;
+ try {
+ if (sm == null) {
+ registry.notifyHooksPrivileged(this);
+ } else {
+ try {
+ AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
+ public Object run() {
+ registry.notifyHooksPrivileged(WovenClassImpl.this);
+ return null;
+ }
+ });
+ } catch (PrivilegedActionException e) {
+ throw (RuntimeException) e.getException();
+ }
+ }
+ } finally {
+ if ((hookFlags & FLAG_HOOKCALLED) != 0) {
+ wovenBytes = resultBytes;
+ newImports = dynamicImports;
+ setHooksComplete();
+ }
+ }
+
+ if (error != null)
+ throw error;
+
+ if (newImports != null) {
+ // add any new dynamic imports
+ for (String newImport : newImports) {
+ try {
+ ManifestElement[] importElements = ManifestElement.parseHeader(Constants.IMPORT_PACKAGE, newImport);
+ loader.addDynamicImportPackage(importElements);
+ } catch (BundleException e) {
+ // should not have happened; checked at add.
+ }
+ }
+ }
+
+ return wovenBytes;
+ }
+
+ public String toString() {
+ return className;
+ }
+
+ public ServiceRegistration<?> getErrorHook() {
+ return errorHook;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/BundleLoader.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/BundleLoader.java
new file mode 100644
index 000000000..f972160de
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/BundleLoader.java
@@ -0,0 +1,1270 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.loader;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.*;
+import org.eclipse.osgi.framework.adaptor.*;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.framework.internal.core.*;
+import org.eclipse.osgi.framework.internal.core.Constants;
+import org.eclipse.osgi.framework.util.KeyedElement;
+import org.eclipse.osgi.framework.util.KeyedHashSet;
+import org.eclipse.osgi.internal.loader.buddy.PolicyHandler;
+import org.eclipse.osgi.internal.resolver.StateBuilder;
+import org.eclipse.osgi.service.resolver.*;
+import org.eclipse.osgi.util.ManifestElement;
+import org.osgi.framework.*;
+import org.osgi.framework.wiring.BundleWiring;
+
+/**
+ * This object is responsible for all classloader delegation for a bundle.
+ * It represents the loaded state of the bundle. BundleLoader objects
+ * are created lazily; care should be taken not to force the creation
+ * of a BundleLoader unless it is necessary.
+ * @see org.eclipse.osgi.internal.loader.BundleLoaderProxy
+ */
+public class BundleLoader implements ClassLoaderDelegate {
+ public final static String DEFAULT_PACKAGE = "."; //$NON-NLS-1$
+ public final static String JAVA_PACKAGE = "java."; //$NON-NLS-1$
+ public final static byte FLAG_IMPORTSINIT = 0x01;
+ public final static byte FLAG_HASDYNAMICIMPORTS = 0x02;
+ public final static byte FLAG_HASDYNAMICEIMPORTALL = 0x04;
+ public final static byte FLAG_CLOSED = 0x08;
+ public final static byte FLAG_LAZYTRIGGER = 0x10;
+
+ public final static ClassContext CLASS_CONTEXT = AccessController.doPrivileged(new PrivilegedAction<ClassContext>() {
+ public ClassContext run() {
+ return new ClassContext();
+ }
+ });
+ public final static ClassLoader FW_CLASSLOADER = getClassLoader(Framework.class);
+
+ private static final int PRE_CLASS = 1;
+ private static final int POST_CLASS = 2;
+ private static final int PRE_RESOURCE = 3;
+ private static final int POST_RESOURCE = 4;
+ private static final int PRE_RESOURCES = 5;
+ private static final int POST_RESOURCES = 6;
+ private static final int PRE_LIBRARY = 7;
+ private static final int POST_LIBRARY = 8;
+
+ /* the proxy */
+ final private BundleLoaderProxy proxy;
+ /* Bundle object */
+ final BundleHost bundle;
+ final private PolicyHandler policy;
+ /* List of package names that are exported by this BundleLoader */
+ final private Collection<String> exportedPackages;
+ final private Collection<String> substitutedPackages;
+ /* List of required bundle BundleLoaderProxy objects */
+ final BundleLoaderProxy[] requiredBundles;
+ /* List of indexes into the requiredBundles list of reexported bundles */
+ final int[] reexportTable;
+ /* cache of required package sources. Key is packagename, value is PackageSource */
+ final private KeyedHashSet requiredSources;
+
+ // note that the following non-final must be access using synchronization
+ /* cache of imported packages. Key is packagename, Value is PackageSource */
+ private KeyedHashSet importedSources;
+ /* If not null, list of package stems to import dynamically. */
+ private String[] dynamicImportPackageStems;
+ /* If not null, list of package names to import dynamically. */
+ private String[] dynamicImportPackages;
+ /* loader flags */
+ private byte loaderFlags = 0;
+ /* The is the BundleClassLoader for the bundle */
+ private BundleClassLoader classloader;
+ private ClassLoader parent;
+
+ /**
+ * Returns the package name from the specified class name.
+ * The returned package is dot seperated.
+ *
+ * @param name Name of a class.
+ * @return Dot separated package name or null if the class
+ * has no package name.
+ */
+ public final static String getPackageName(String name) {
+ if (name != null) {
+ int index = name.lastIndexOf('.'); /* find last period in class name */
+ if (index > 0)
+ return name.substring(0, index);
+ }
+ return DEFAULT_PACKAGE;
+ }
+
+ /**
+ * Returns the package name from the specified resource name.
+ * The returned package is dot seperated.
+ *
+ * @param name Name of a resource.
+ * @return Dot separated package name or null if the resource
+ * has no package name.
+ */
+ public final static String getResourcePackageName(String name) {
+ if (name != null) {
+ /* check for leading slash*/
+ int begin = ((name.length() > 1) && (name.charAt(0) == '/')) ? 1 : 0;
+ int end = name.lastIndexOf('/'); /* index of last slash */
+ if (end > begin)
+ return name.substring(begin, end).replace('/', '.');
+ }
+ return DEFAULT_PACKAGE;
+ }
+
+ /**
+ * BundleLoader runtime constructor. This object is created lazily
+ * when the first request for a resource is made to this bundle.
+ *
+ * @param bundle Bundle object for this loader.
+ * @param proxy the BundleLoaderProxy for this loader.
+ * @exception org.osgi.framework.BundleException
+ */
+ protected BundleLoader(BundleHost bundle, BundleLoaderProxy proxy) throws BundleException {
+ this.bundle = bundle;
+ this.proxy = proxy;
+ try {
+ bundle.getBundleData().open(); /* make sure the BundleData is open */
+ } catch (IOException e) {
+ throw new BundleException(Msg.BUNDLE_READ_EXCEPTION, e);
+ }
+ BundleDescription description = proxy.getBundleDescription();
+ // init the require bundles list.
+ BundleDescription[] required = description.getResolvedRequires();
+ if (required.length > 0) {
+ // get a list of re-exported symbolic names
+ Set<String> reExportSet = new HashSet<String>(required.length);
+ BundleSpecification[] requiredSpecs = description.getRequiredBundles();
+ if (requiredSpecs != null && requiredSpecs.length > 0)
+ for (int i = 0; i < requiredSpecs.length; i++)
+ if (requiredSpecs[i].isExported())
+ reExportSet.add(requiredSpecs[i].getName());
+
+ requiredBundles = new BundleLoaderProxy[required.length];
+ int[] reexported = new int[required.length];
+ int reexportIndex = 0;
+ for (int i = 0; i < required.length; i++) {
+ requiredBundles[i] = getLoaderProxy(required[i]);
+ if (reExportSet.contains(required[i].getSymbolicName()))
+ reexported[reexportIndex++] = i;
+ }
+ if (reexportIndex > 0) {
+ reexportTable = new int[reexportIndex];
+ System.arraycopy(reexported, 0, reexportTable, 0, reexportIndex);
+ } else {
+ reexportTable = null;
+ }
+ requiredSources = new KeyedHashSet(10, false);
+ } else {
+ requiredBundles = null;
+ reexportTable = null;
+ requiredSources = null;
+ }
+
+ // init the provided packages set
+ ExportPackageDescription[] exports = description.getSelectedExports();
+ if (exports != null && exports.length > 0) {
+ exportedPackages = Collections.synchronizedCollection(exports.length > 10 ? new HashSet<String>(exports.length) : new ArrayList<String>(exports.length));
+ initializeExports(exports, exportedPackages);
+ } else {
+ exportedPackages = Collections.synchronizedCollection(new ArrayList<String>(0));
+ }
+
+ ExportPackageDescription substituted[] = description.getSubstitutedExports();
+ if (substituted.length > 0) {
+ substitutedPackages = substituted.length > 10 ? new HashSet<String>(substituted.length) : new ArrayList<String>(substituted.length);
+ for (int i = 0; i < substituted.length; i++)
+ substitutedPackages.add(substituted[i].getName());
+ } else {
+ substitutedPackages = null;
+ }
+
+ //This is the fastest way to access to the description for fragments since the hostdescription.getFragments() is slow
+ BundleFragment[] fragmentObjects = bundle.getFragments();
+ BundleDescription[] fragments = new BundleDescription[fragmentObjects == null ? 0 : fragmentObjects.length];
+ for (int i = 0; i < fragments.length; i++)
+ fragments[i] = fragmentObjects[i].getBundleDescription();
+ // init the dynamic imports tables
+ if (description.hasDynamicImports())
+ addDynamicImportPackage(description.getImportPackages());
+ // ...and its fragments
+ for (int i = 0; i < fragments.length; i++)
+ if (fragments[i].isResolved() && fragments[i].hasDynamicImports())
+ addDynamicImportPackage(fragments[i].getImportPackages());
+
+ //Initialize the policy handler
+ String buddyList = null;
+ try {
+ buddyList = bundle.getBundleData().getManifest().get(Constants.BUDDY_LOADER);
+ } catch (BundleException e) {
+ // do nothing; buddyList == null
+ }
+ policy = buddyList != null ? new PolicyHandler(this, buddyList, bundle.getFramework().getPackageAdmin()) : null;
+ if (policy != null)
+ policy.open(bundle.getFramework().getSystemBundleContext());
+ }
+
+ private void initializeExports(ExportPackageDescription[] exports, Collection<String> exportNames) {
+ for (int i = 0; i < exports.length; i++) {
+ if (proxy.forceSourceCreation(exports[i])) {
+ if (!exportNames.contains(exports[i].getName())) {
+ // must force filtered and reexport sources to be created early
+ // to prevent lazy normal package source creation.
+ // We only do this for the first export of a package name.
+ proxy.createPackageSource(exports[i], true);
+ }
+ }
+ exportNames.add(exports[i].getName());
+ }
+ }
+
+ public synchronized KeyedHashSet getImportedSources(KeyedHashSet visited) {
+ if ((loaderFlags & FLAG_IMPORTSINIT) != 0)
+ return importedSources;
+ BundleDescription bundleDesc = proxy.getBundleDescription();
+ ExportPackageDescription[] packages = bundleDesc.getResolvedImports();
+ if (packages != null && packages.length > 0) {
+ if (importedSources == null)
+ importedSources = new KeyedHashSet(packages.length, false);
+ for (int i = 0; i < packages.length; i++) {
+ if (packages[i].getExporter() == bundleDesc)
+ continue; // ignore imports resolved to this bundle
+ PackageSource source = createExportPackageSource(packages[i], visited);
+ if (source != null)
+ importedSources.add(source);
+ }
+ }
+ loaderFlags |= FLAG_IMPORTSINIT;
+ return importedSources;
+ }
+
+ public synchronized boolean isLazyTriggerSet() {
+ return (loaderFlags & FLAG_LAZYTRIGGER) != 0;
+ }
+
+ public void setLazyTrigger() throws BundleException {
+ synchronized (this) {
+ loaderFlags |= FLAG_LAZYTRIGGER;
+ }
+ BundleLoaderProxy.secureAction.start(bundle, Bundle.START_TRANSIENT | BundleHost.LAZY_TRIGGER);
+ }
+
+ final PackageSource createExportPackageSource(ExportPackageDescription export, KeyedHashSet visited) {
+ BundleLoaderProxy exportProxy = getLoaderProxy(export.getExporter());
+ if (exportProxy == null)
+ // TODO log error!!
+ return null;
+ PackageSource requiredSource = exportProxy.getBundleLoader().findRequiredSource(export.getName(), visited);
+ PackageSource exportSource = exportProxy.createPackageSource(export, false);
+ if (requiredSource == null)
+ return exportSource;
+ return createMultiSource(export.getName(), new PackageSource[] {requiredSource, exportSource});
+ }
+
+ private static PackageSource createMultiSource(String packageName, PackageSource[] sources) {
+ if (sources.length == 1)
+ return sources[0];
+ List<SingleSourcePackage> sourceList = new ArrayList<SingleSourcePackage>(sources.length);
+ for (int i = 0; i < sources.length; i++) {
+ SingleSourcePackage[] innerSources = sources[i].getSuppliers();
+ for (int j = 0; j < innerSources.length; j++)
+ if (!sourceList.contains(innerSources[j]))
+ sourceList.add(innerSources[j]);
+ }
+ return new MultiSourcePackage(packageName, sourceList.toArray(new SingleSourcePackage[sourceList.size()]));
+ }
+
+ /*
+ * get the loader proxy for a bundle description
+ */
+ public final BundleLoaderProxy getLoaderProxy(BundleDescription source) {
+ Object userObject = source.getUserObject();
+ if (!(userObject instanceof BundleLoaderProxy)) {
+ // may need to force the proxy to be created
+ long exportingID = source.getBundleId();
+ BundleHost exportingBundle = (BundleHost) bundle.getFramework().getBundle(exportingID);
+ if (exportingBundle == null)
+ return null;
+ userObject = exportingBundle.getLoaderProxy();
+ }
+ return (BundleLoaderProxy) userObject;
+ }
+
+ public BundleLoaderProxy getLoaderProxy() {
+ return proxy;
+ }
+
+ /*
+ * Close the the BundleLoader.
+ *
+ */
+ synchronized void close() {
+ if ((loaderFlags & FLAG_CLOSED) != 0)
+ return;
+ if (classloader != null)
+ classloader.close();
+ if (policy != null)
+ policy.close(bundle.getFramework().getSystemBundleContext());
+ loaderFlags |= FLAG_CLOSED; /* This indicates the BundleLoader is destroyed */
+ }
+
+ /**
+ * This method loads a class from the bundle. The class is searched for in the
+ * same manner as it would if it was being loaded from a bundle (i.e. all
+ * hosts, fragments, import, required bundles and local resources are searched.
+ *
+ * @param name the name of the desired Class.
+ * @return the resulting Class
+ * @exception java.lang.ClassNotFoundException if the class definition was not found.
+ */
+ final public Class<?> loadClass(String name) throws ClassNotFoundException {
+ BundleClassLoader bcl = createClassLoader();
+ // The instanceof check here is just to be safe. The javadoc contract stated in BundleClassLoader
+ // mandate that BundleClassLoaders be an instance of ClassLoader.
+ if (name.length() > 0 && name.charAt(0) == '[' && bcl instanceof ClassLoader)
+ return Class.forName(name, false, (ClassLoader) bcl);
+ return bcl.loadClass(name);
+ }
+
+ /**
+ * This method gets a resource from the bundle. The resource is searched
+ * for in the same manner as it would if it was being loaded from a bundle
+ * (i.e. all hosts, fragments, import, required bundles and
+ * local resources are searched).
+ *
+ * @param name the name of the desired resource.
+ * @return the resulting resource URL or null if it does not exist.
+ */
+ final URL getResource(String name) {
+ return createClassLoader().getResource(name);
+ }
+
+ public final synchronized ClassLoader getParentClassLoader() {
+ if (parent != null)
+ return parent;
+ createClassLoader();
+ return parent;
+ }
+
+ final public synchronized BundleClassLoader createClassLoader() {
+ if (classloader != null)
+ return classloader;
+ String[] classpath;
+ try {
+ classpath = bundle.getBundleData().getClassPath();
+ } catch (BundleException e) {
+ // no classpath
+ classpath = new String[0];
+ bundle.getFramework().publishFrameworkEvent(FrameworkEvent.ERROR, bundle, e);
+ }
+ if (classpath == null) {
+ // no classpath
+ classpath = new String[0];
+ bundle.getFramework().publishFrameworkEvent(FrameworkEvent.ERROR, bundle, new BundleException(Msg.BUNDLE_NO_CLASSPATH_MATCH, BundleException.MANIFEST_ERROR));
+ }
+ BundleClassLoader bcl = createBCLPrevileged(bundle.getProtectionDomain(), classpath);
+ parent = getParentPrivileged(bcl);
+ classloader = bcl;
+ return classloader;
+ }
+
+ /**
+ * Finds a class local to this bundle. Only the classloader for this bundle is searched.
+ * @param name The name of the class to find.
+ * @return The loaded Class or null if the class is not found.
+ * @throws ClassNotFoundException
+ */
+ Class<?> findLocalClass(String name) throws ClassNotFoundException {
+ if (Debug.DEBUG_LOADER)
+ Debug.println("BundleLoader[" + this + "].findLocalClass(" + name + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ try {
+ Class<?> clazz = createClassLoader().findLocalClass(name);
+ if (Debug.DEBUG_LOADER && clazz != null)
+ Debug.println("BundleLoader[" + this + "] found local class " + name); //$NON-NLS-1$ //$NON-NLS-2$
+ return clazz;
+ } catch (ClassNotFoundException e) {
+ if (e instanceof StatusException) {
+ if ((((StatusException) e).getStatusCode() & StatusException.CODE_ERROR) != 0)
+ throw e;
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Finds the class for a bundle. This method is used for delegation by the bundle's classloader.
+ */
+ public Class<?> findClass(String name) throws ClassNotFoundException {
+ return findClass(name, true);
+ }
+
+ Class<?> findClass(String name, boolean checkParent) throws ClassNotFoundException {
+ ClassLoader parentCL = getParentClassLoader();
+ if (checkParent && parentCL != null && name.startsWith(JAVA_PACKAGE))
+ // 1) if startsWith "java." delegate to parent and terminate search
+ // we want to throw ClassNotFoundExceptions if a java.* class cannot be loaded from the parent.
+ return parentCL.loadClass(name);
+ return findClassInternal(name, checkParent, parentCL);
+ }
+
+ private Class<?> findClassInternal(String name, boolean checkParent, ClassLoader parentCL) throws ClassNotFoundException {
+ if (Debug.DEBUG_LOADER)
+ Debug.println("BundleLoader[" + this + "].loadBundleClass(" + name + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ String pkgName = getPackageName(name);
+ boolean bootDelegation = false;
+ // follow the OSGi delegation model
+ if (checkParent && parentCL != null && bundle.getFramework().isBootDelegationPackage(pkgName))
+ // 2) if part of the bootdelegation list then delegate to parent and continue of failure
+ try {
+ return parentCL.loadClass(name);
+ } catch (ClassNotFoundException cnfe) {
+ // we want to continue
+ bootDelegation = true;
+ }
+ Class<?> result = null;
+ try {
+ result = (Class<?>) searchHooks(name, PRE_CLASS);
+ } catch (ClassNotFoundException e) {
+ throw e;
+ } catch (FileNotFoundException e) {
+ // will not happen
+ }
+ if (result != null)
+ return result;
+ // 3) search the imported packages
+ PackageSource source = findImportedSource(pkgName, null);
+ if (source != null) {
+ // 3) found import source terminate search at the source
+ result = source.loadClass(name);
+ if (result != null)
+ return result;
+ throw new ClassNotFoundException(name);
+ }
+ // 4) search the required bundles
+ source = findRequiredSource(pkgName, null);
+ if (source != null)
+ // 4) attempt to load from source but continue on failure
+ result = source.loadClass(name);
+ // 5) search the local bundle
+ if (result == null)
+ result = findLocalClass(name);
+ if (result != null)
+ return result;
+ // 6) attempt to find a dynamic import source; only do this if a required source was not found
+ if (source == null) {
+ source = findDynamicSource(pkgName);
+ if (source != null) {
+ result = source.loadClass(name);
+ if (result != null)
+ return result;
+ // must throw CNFE if dynamic import source does not have the class
+ throw new ClassNotFoundException(name);
+ }
+ }
+
+ if (result == null)
+ try {
+ result = (Class<?>) searchHooks(name, POST_CLASS);
+ } catch (ClassNotFoundException e) {
+ throw e;
+ } catch (FileNotFoundException e) {
+ // will not happen
+ }
+ // do buddy policy loading
+ if (result == null && policy != null)
+ result = policy.doBuddyClassLoading(name);
+ if (result != null)
+ return result;
+ // hack to support backwards compatibiility for bootdelegation
+ // or last resort; do class context trick to work around VM bugs
+ if (parentCL != null && !bootDelegation && ((checkParent && bundle.getFramework().compatibiltyBootDelegation) || isRequestFromVM()))
+ // we don't need to continue if a CNFE is thrown here.
+ try {
+ return parentCL.loadClass(name);
+ } catch (ClassNotFoundException e) {
+ // we want to generate our own exception below
+ }
+ throw new ClassNotFoundException(name);
+ }
+
+ @SuppressWarnings("unchecked")
+ private <E> E searchHooks(String name, int type) throws ClassNotFoundException, FileNotFoundException {
+ ClassLoaderDelegateHook[] delegateHooks = bundle.getFramework().getDelegateHooks();
+ if (delegateHooks == null)
+ return null;
+ E result = null;
+ for (int i = 0; i < delegateHooks.length && result == null; i++) {
+ switch (type) {
+ case PRE_CLASS :
+ result = (E) delegateHooks[i].preFindClass(name, createClassLoader(), bundle.getBundleData());
+ break;
+ case POST_CLASS :
+ result = (E) delegateHooks[i].postFindClass(name, createClassLoader(), bundle.getBundleData());
+ break;
+ case PRE_RESOURCE :
+ result = (E) delegateHooks[i].preFindResource(name, createClassLoader(), bundle.getBundleData());
+ break;
+ case POST_RESOURCE :
+ result = (E) delegateHooks[i].postFindResource(name, createClassLoader(), bundle.getBundleData());
+ break;
+ case PRE_RESOURCES :
+ result = (E) delegateHooks[i].preFindResources(name, createClassLoader(), bundle.getBundleData());
+ break;
+ case POST_RESOURCES :
+ result = (E) delegateHooks[i].postFindResources(name, createClassLoader(), bundle.getBundleData());
+ break;
+ case PRE_LIBRARY :
+ result = (E) delegateHooks[i].preFindLibrary(name, createClassLoader(), bundle.getBundleData());
+ break;
+ case POST_LIBRARY :
+ result = (E) delegateHooks[i].postFindLibrary(name, createClassLoader(), bundle.getBundleData());
+ break;
+ }
+ }
+ return result;
+ }
+
+ private boolean isRequestFromVM() {
+ if (bundle.getFramework().isBootDelegationPackage("*") || !bundle.getFramework().contextBootDelegation) //$NON-NLS-1$
+ return false;
+ // works around VM bugs that require all classloaders to have access to parent packages
+ Class<?>[] context = CLASS_CONTEXT.getClassContext();
+ if (context == null || context.length < 2)
+ return false;
+ // skip the first class; it is the ClassContext class
+ for (int i = 1; i < context.length; i++)
+ // find the first class in the context which is not BundleLoader or instanceof ClassLoader
+ if (context[i] != BundleLoader.class && !ClassLoader.class.isAssignableFrom(context[i])) {
+ // only find in parent if the class is not "Class" (Class#forName case) or if the class is not loaded with a BundleClassLoader
+ ClassLoader cl = getClassLoader(context[i]);
+ if (cl != FW_CLASSLOADER) { // extra check incase an adaptor adds another class into the stack besides an instance of ClassLoader
+ if (Class.class != context[i] && !(cl instanceof BundleClassLoader))
+ return true;
+ break;
+ }
+ }
+ return false;
+ }
+
+ private static ClassLoader getClassLoader(final Class<?> clazz) {
+ if (System.getSecurityManager() == null)
+ return clazz.getClassLoader();
+ return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
+ public ClassLoader run() {
+ return clazz.getClassLoader();
+ }
+ });
+ }
+
+ /**
+ * Finds the resource for a bundle. This method is used for delegation by the bundle's classloader.
+ */
+ public URL findResource(String name) {
+ return findResource(name, true);
+ }
+
+ URL findResource(String name, boolean checkParent) {
+ if ((name.length() > 1) && (name.charAt(0) == '/')) /* if name has a leading slash */
+ name = name.substring(1); /* remove leading slash before search */
+ String pkgName = getResourcePackageName(name);
+ boolean bootDelegation = false;
+ ClassLoader parentCL = getParentClassLoader();
+ // follow the OSGi delegation model
+ // First check the parent classloader for system resources, if it is a java resource.
+ if (checkParent && parentCL != null) {
+ if (pkgName.startsWith(JAVA_PACKAGE))
+ // 1) if startsWith "java." delegate to parent and terminate search
+ // we never delegate java resource requests past the parent
+ return parentCL.getResource(name);
+ else if (bundle.getFramework().isBootDelegationPackage(pkgName)) {
+ // 2) if part of the bootdelegation list then delegate to parent and continue of failure
+ URL result = parentCL.getResource(name);
+ if (result != null)
+ return result;
+ bootDelegation = true;
+ }
+ }
+
+ URL result = null;
+ try {
+ result = (URL) searchHooks(name, PRE_RESOURCE);
+ } catch (FileNotFoundException e) {
+ return null;
+ } catch (ClassNotFoundException e) {
+ // will not happen
+ }
+ if (result != null)
+ return result;
+ // 3) search the imported packages
+ PackageSource source = findImportedSource(pkgName, null);
+ if (source != null)
+ // 3) found import source terminate search at the source
+ return source.getResource(name);
+ // 4) search the required bundles
+ source = findRequiredSource(pkgName, null);
+ if (source != null)
+ // 4) attempt to load from source but continue on failure
+ result = source.getResource(name);
+ // 5) search the local bundle
+ if (result == null)
+ result = findLocalResource(name);
+ if (result != null)
+ return result;
+ // 6) attempt to find a dynamic import source; only do this if a required source was not found
+ if (source == null) {
+ source = findDynamicSource(pkgName);
+ if (source != null)
+ // must return the result of the dynamic import and do not continue
+ return source.getResource(name);
+ }
+
+ if (result == null)
+ try {
+ result = (URL) searchHooks(name, POST_RESOURCE);
+ } catch (FileNotFoundException e) {
+ return null;
+ } catch (ClassNotFoundException e) {
+ // will not happen
+ }
+ // do buddy policy loading
+ if (result == null && policy != null)
+ result = policy.doBuddyResourceLoading(name);
+ if (result != null)
+ return result;
+ // hack to support backwards compatibiility for bootdelegation
+ // or last resort; do class context trick to work around VM bugs
+ if (parentCL != null && !bootDelegation && ((checkParent && bundle.getFramework().compatibiltyBootDelegation) || isRequestFromVM()))
+ // we don't need to continue if the resource is not found here
+ return parentCL.getResource(name);
+ return result;
+ }
+
+ /**
+ * Finds the resources for a bundle. This method is used for delegation by the bundle's classloader.
+ */
+ public Enumeration<URL> findResources(String name) throws IOException {
+ // do not delegate to parent because ClassLoader#getResources already did and it is final!!
+ if ((name.length() > 1) && (name.charAt(0) == '/')) /* if name has a leading slash */
+ name = name.substring(1); /* remove leading slash before search */
+ String pkgName = getResourcePackageName(name);
+ Enumeration<URL> result = null;
+ try {
+ result = searchHooks(name, PRE_RESOURCES);
+ } catch (ClassNotFoundException e) {
+ // will not happen
+ } catch (FileNotFoundException e) {
+ return null;
+ }
+ if (result != null)
+ return result;
+ // start at step 3 because of the comment above about ClassLoader#getResources
+ // 3) search the imported packages
+ PackageSource source = findImportedSource(pkgName, null);
+ if (source != null)
+ // 3) found import source terminate search at the source
+ return source.getResources(name);
+ // 4) search the required bundles
+ source = findRequiredSource(pkgName, null);
+ if (source != null)
+ // 4) attempt to load from source but continue on failure
+ result = source.getResources(name);
+
+ // 5) search the local bundle
+ // compound the required source results with the local ones
+ Enumeration<URL> localResults = findLocalResources(name);
+ result = compoundEnumerations(result, localResults);
+ // 6) attempt to find a dynamic import source; only do this if a required source was not found
+ if (result == null && source == null) {
+ source = findDynamicSource(pkgName);
+ if (source != null)
+ return source.getResources(name);
+ }
+ if (result == null)
+ try {
+ result = searchHooks(name, POST_RESOURCES);
+ } catch (ClassNotFoundException e) {
+ // will not happen
+ } catch (FileNotFoundException e) {
+ return null;
+ }
+ if (policy != null) {
+ Enumeration<URL> buddyResult = policy.doBuddyResourcesLoading(name);
+ result = compoundEnumerations(result, buddyResult);
+ }
+ return result;
+ }
+
+ private boolean isSubPackage(String parentPackage, String subPackage) {
+ String prefix = (parentPackage.length() == 0 || parentPackage.equals(DEFAULT_PACKAGE)) ? "" : parentPackage + '.'; //$NON-NLS-1$
+ return subPackage.startsWith(prefix);
+ }
+
+ public Collection<String> listResources(String path, String filePattern, int options) {
+ String pkgName = getResourcePackageName(path.endsWith("/") ? path : path + '/'); //$NON-NLS-1$
+ if ((path.length() > 1) && (path.charAt(0) == '/')) /* if name has a leading slash */
+ path = path.substring(1); /* remove leading slash before search */
+ boolean subPackages = (options & BundleWiring.LISTRESOURCES_RECURSE) != 0;
+ List<String> packages = new ArrayList<String>();
+ // search imported package names
+ KeyedHashSet importSources = getImportedSources(null);
+ if (importSources != null) {
+ KeyedElement[] imports = importSources.elements();
+ for (KeyedElement keyedElement : imports) {
+ String id = ((PackageSource) keyedElement).getId();
+ if (id.equals(pkgName) || (subPackages && isSubPackage(pkgName, id)))
+ packages.add(id);
+ }
+ }
+
+ // now add package names from required bundles
+ if (requiredBundles != null) {
+ KeyedHashSet visited = new KeyedHashSet(false);
+ visited.add(bundle); // always add ourselves so we do not recurse back to ourselves
+ for (BundleLoaderProxy requiredProxy : requiredBundles) {
+ BundleLoader requiredLoader = requiredProxy.getBundleLoader();
+ requiredLoader.addProvidedPackageNames(requiredProxy.getSymbolicName(), pkgName, packages, subPackages, visited);
+ }
+ }
+
+ boolean localSearch = (options & BundleWiring.LISTRESOURCES_LOCAL) != 0;
+ List<String> result = new ArrayList<String>();
+ Set<String> importedPackages = new HashSet<String>(0);
+ for (String name : packages) {
+ // look for import source
+ PackageSource externalSource = findImportedSource(name, null);
+ if (externalSource != null) {
+ // record this package is imported
+ importedPackages.add(name);
+ } else {
+ // look for require bundle source
+ externalSource = findRequiredSource(name, null);
+ }
+ // only add the content of the external source if this is not a localSearch
+ if (externalSource != null && !localSearch) {
+ String packagePath = name.replace('.', '/');
+ Collection<String> externalResources = externalSource.listResources(packagePath, filePattern);
+ for (String resource : externalResources) {
+ if (!result.contains(resource)) // prevent duplicates; could happen if the package is split or exporter has fragments/multiple jars
+ result.add(resource);
+ }
+ }
+ }
+
+ // now search locally
+ Collection<String> localResources = createClassLoader().listLocalResources(path, filePattern, options);
+ for (String resource : localResources) {
+ String resourcePkg = getResourcePackageName(resource);
+ if (!importedPackages.contains(resourcePkg) && !result.contains(resource))
+ result.add(resource);
+ }
+ return result;
+ }
+
+ /*
+ * This method is used by Bundle.getResources to do proper parent delegation.
+ */
+ public Enumeration<URL> getResources(String name) throws IOException {
+ if ((name.length() > 1) && (name.charAt(0) == '/')) /* if name has a leading slash */
+ name = name.substring(1); /* remove leading slash before search */
+ String pkgName = getResourcePackageName(name);
+ // follow the OSGi delegation model
+ // First check the parent classloader for system resources, if it is a java resource.
+ Enumeration<URL> result = null;
+ if (pkgName.startsWith(JAVA_PACKAGE) || bundle.getFramework().isBootDelegationPackage(pkgName)) {
+ // 1) if startsWith "java." delegate to parent and terminate search
+ // 2) if part of the bootdelegation list then delegate to parent and continue of failure
+ ClassLoader parentCL = getParentClassLoader();
+ result = parentCL == null ? null : parentCL.getResources(name);
+ if (pkgName.startsWith(JAVA_PACKAGE))
+ return result;
+ }
+ return compoundEnumerations(result, findResources(name));
+ }
+
+ public static <E> Enumeration<E> compoundEnumerations(Enumeration<E> list1, Enumeration<E> list2) {
+ if (list2 == null || !list2.hasMoreElements())
+ return list1;
+ if (list1 == null || !list1.hasMoreElements())
+ return list2;
+ List<E> compoundResults = new ArrayList<E>();
+ while (list1.hasMoreElements())
+ compoundResults.add(list1.nextElement());
+ while (list2.hasMoreElements()) {
+ E item = list2.nextElement();
+ if (!compoundResults.contains(item)) //don't add duplicates
+ compoundResults.add(item);
+ }
+ return Collections.enumeration(compoundResults);
+ }
+
+ /**
+ * Finds a resource local to this bundle. Only the classloader for this bundle is searched.
+ * @param name The name of the resource to find.
+ * @return The URL to the resource or null if the resource is not found.
+ */
+ URL findLocalResource(final String name) {
+ return createClassLoader().findLocalResource(name);
+ }
+
+ /**
+ * Returns an Enumeration of URLs representing all the resources with
+ * the given name. Only the classloader for this bundle is searched.
+ *
+ * @param name the resource name
+ * @return an Enumeration of URLs for the resources
+ */
+ Enumeration<URL> findLocalResources(String name) {
+ return createClassLoader().findLocalResources(name);
+ }
+
+ /**
+ * Returns the absolute path name of a native library.
+ *
+ * @param name the library name
+ * @return the absolute path of the native library or null if not found
+ */
+ public String findLibrary(final String name) {
+ if (System.getSecurityManager() == null)
+ return findLocalLibrary(name);
+ return AccessController.doPrivileged(new PrivilegedAction<String>() {
+ public String run() {
+ return findLocalLibrary(name);
+ }
+ });
+ }
+
+ final String findLocalLibrary(final String name) {
+ String result = null;
+ try {
+ result = (String) searchHooks(name, PRE_LIBRARY);
+ } catch (FileNotFoundException e) {
+ return null;
+ } catch (ClassNotFoundException e) {
+ // will not happen
+ }
+ if (result != null)
+ return result;
+ result = bundle.getBundleData().findLibrary(name);
+ if (result != null)
+ return result;
+
+ // look in fragments imports ...
+ BundleFragment[] fragments = bundle.getFragments();
+ if (fragments != null)
+ for (int i = 0; i < fragments.length; i++) {
+ result = fragments[i].getBundleData().findLibrary(name);
+ if (result != null)
+ return result;
+ }
+ try {
+ return (String) searchHooks(name, POST_LIBRARY);
+ } catch (FileNotFoundException e) {
+ return null; // this is not necessary; but being consistent in case another step is added below
+ } catch (ClassNotFoundException e) {
+ // will not happen
+ }
+ return null;
+ }
+
+ /*
+ * Return the bundle we are associated with.
+ */
+ public final AbstractBundle getBundle() {
+ return bundle;
+ }
+
+ private BundleClassLoader createBCLPrevileged(final BundleProtectionDomain pd, final String[] cp) {
+ // Create the classloader as previleged code if security manager is present.
+ if (System.getSecurityManager() == null)
+ return createBCL(pd, cp);
+
+ return AccessController.doPrivileged(new PrivilegedAction<BundleClassLoader>() {
+ public BundleClassLoader run() {
+ return createBCL(pd, cp);
+ }
+ });
+
+ }
+
+ BundleClassLoader createBCL(final BundleProtectionDomain pd, final String[] cp) {
+ BundleClassLoader bcl = bundle.getBundleData().createClassLoader(BundleLoader.this, pd, cp);
+ // attach existing fragments to classloader
+ BundleFragment[] fragments = bundle.getFragments();
+ if (fragments != null)
+ for (int i = 0; i < fragments.length; i++) {
+ try {
+ bcl.attachFragment(fragments[i].getBundleData(), fragments[i].getProtectionDomain(), fragments[i].getBundleData().getClassPath());
+ } catch (BundleException be) {
+ bundle.getFramework().publishFrameworkEvent(FrameworkEvent.ERROR, bundle, be);
+ }
+ }
+
+ // finish the initialization of the classloader.
+ bcl.initialize();
+ return bcl;
+ }
+
+ /**
+ * Return a string representation of this loader.
+ * @return String
+ */
+ public final String toString() {
+ BundleData result = bundle.getBundleData();
+ return result == null ? "BundleLoader.bundledata == null!" : result.toString(); //$NON-NLS-1$
+ }
+
+ /**
+ * Return true if the target package name matches
+ * a name in the DynamicImport-Package manifest header.
+ *
+ * @param pkgname The name of the requested class' package.
+ * @return true if the package should be imported.
+ */
+ private final synchronized boolean isDynamicallyImported(String pkgname) {
+ if (this instanceof SystemBundleLoader)
+ return false; // system bundle cannot dynamically import
+ // must check for startsWith("java.") to satisfy R3 section 4.7.2
+ if (pkgname.startsWith("java.")) //$NON-NLS-1$
+ return true;
+
+ /* quick shortcut check */
+ if ((loaderFlags & FLAG_HASDYNAMICIMPORTS) == 0)
+ return false;
+
+ /* "*" shortcut */
+ if ((loaderFlags & FLAG_HASDYNAMICEIMPORTALL) != 0)
+ return true;
+
+ /* match against specific names */
+ if (dynamicImportPackages != null)
+ for (int i = 0; i < dynamicImportPackages.length; i++)
+ if (pkgname.equals(dynamicImportPackages[i]))
+ return true;
+
+ /* match against names with trailing wildcards */
+ if (dynamicImportPackageStems != null)
+ for (int i = 0; i < dynamicImportPackageStems.length; i++)
+ if (pkgname.startsWith(dynamicImportPackageStems[i]))
+ return true;
+
+ return false;
+ }
+
+ final void addExportedProvidersFor(String symbolicName, String packageName, List<PackageSource> result, KeyedHashSet visited) {
+ if (!visited.add(bundle))
+ return;
+
+ // See if we locally provide the package.
+ PackageSource local = null;
+ if (isExportedPackage(packageName))
+ local = proxy.getPackageSource(packageName);
+ else if (isSubstitutedExport(packageName)) {
+ result.add(findImportedSource(packageName, visited));
+ return; // should not continue to required bundles in this case
+ }
+ // Must search required bundles that are exported first.
+ if (requiredBundles != null) {
+ int size = reexportTable == null ? 0 : reexportTable.length;
+ int reexportIndex = 0;
+ for (int i = 0; i < requiredBundles.length; i++) {
+ if (local != null) {
+ // always add required bundles first if we locally provide the package
+ // This allows a bundle to provide a package from a required bundle without
+ // re-exporting the whole required bundle.
+ requiredBundles[i].getBundleLoader().addExportedProvidersFor(symbolicName, packageName, result, visited);
+ } else if (reexportIndex < size && reexportTable[reexportIndex] == i) {
+ reexportIndex++;
+ requiredBundles[i].getBundleLoader().addExportedProvidersFor(symbolicName, packageName, result, visited);
+ }
+ }
+ }
+
+ // now add the locally provided package.
+ if (local != null && local.isFriend(symbolicName))
+ result.add(local);
+ }
+
+ final void addProvidedPackageNames(String symbolicName, String packageName, List<String> result, boolean subPackages, KeyedHashSet visitied) {
+ if (!visitied.add(bundle))
+ return;
+ for (String exported : exportedPackages) {
+ if (exported.equals(packageName) || (subPackages && isSubPackage(packageName, exported))) {
+ if (!result.contains(exported))
+ result.add(exported);
+ }
+ }
+ if (substitutedPackages != null)
+ for (String substituted : substitutedPackages) {
+ if (substituted.equals(packageName) || (subPackages && isSubPackage(packageName, substituted))) {
+ if (!result.contains(substituted))
+ result.add(substituted);
+ }
+ }
+ if (requiredBundles != null) {
+ int size = reexportTable == null ? 0 : reexportTable.length;
+ int reexportIndex = 0;
+ for (int i = 0; i < requiredBundles.length; i++) {
+ if (reexportIndex < size && reexportTable[reexportIndex] == i) {
+ reexportIndex++;
+ requiredBundles[i].getBundleLoader().addProvidedPackageNames(symbolicName, packageName, result, subPackages, visitied);
+ }
+ }
+ }
+ }
+
+ final boolean isExportedPackage(String name) {
+ return exportedPackages.contains(name);
+ }
+
+ final boolean isSubstitutedExport(String name) {
+ return substitutedPackages == null ? false : substitutedPackages.contains(name);
+ }
+
+ private void addDynamicImportPackage(ImportPackageSpecification[] packages) {
+ if (packages == null)
+ return;
+ List<String> dynamicImports = new ArrayList<String>(packages.length);
+ for (int i = 0; i < packages.length; i++)
+ if (ImportPackageSpecification.RESOLUTION_DYNAMIC.equals(packages[i].getDirective(Constants.RESOLUTION_DIRECTIVE)))
+ dynamicImports.add(packages[i].getName());
+ if (dynamicImports.size() > 0)
+ addDynamicImportPackage(dynamicImports.toArray(new String[dynamicImports.size()]));
+ }
+
+ /**
+ * Adds a list of DynamicImport-Package manifest elements to the dynamic
+ * import tables of this BundleLoader. Duplicate packages are checked and
+ * not added again. This method is not thread safe. Callers should ensure
+ * synchronization when calling this method.
+ * @param packages the DynamicImport-Package elements to add.
+ */
+ private void addDynamicImportPackage(String[] packages) {
+ if (packages == null)
+ return;
+
+ loaderFlags |= FLAG_HASDYNAMICIMPORTS;
+ int size = packages.length;
+ List<String> stems;
+ if (dynamicImportPackageStems == null) {
+ stems = new ArrayList<String>(size);
+ } else {
+ stems = new ArrayList<String>(size + dynamicImportPackageStems.length);
+ for (int i = 0; i < dynamicImportPackageStems.length; i++) {
+ stems.add(dynamicImportPackageStems[i]);
+ }
+ }
+
+ List<String> names;
+ if (dynamicImportPackages == null) {
+ names = new ArrayList<String>(size);
+ } else {
+ names = new ArrayList<String>(size + dynamicImportPackages.length);
+ for (int i = 0; i < dynamicImportPackages.length; i++) {
+ names.add(dynamicImportPackages[i]);
+ }
+ }
+
+ for (int i = 0; i < size; i++) {
+ String name = packages[i];
+ if (isDynamicallyImported(name))
+ continue;
+ if (name.equals("*")) { /* shortcut *///$NON-NLS-1$
+ loaderFlags |= FLAG_HASDYNAMICEIMPORTALL;
+ return;
+ }
+
+ if (name.endsWith(".*")) //$NON-NLS-1$
+ stems.add(name.substring(0, name.length() - 1));
+ else
+ names.add(name);
+ }
+
+ size = stems.size();
+ if (size > 0)
+ dynamicImportPackageStems = stems.toArray(new String[size]);
+
+ size = names.size();
+ if (size > 0)
+ dynamicImportPackages = names.toArray(new String[size]);
+ }
+
+ /**
+ * Adds a list of DynamicImport-Package manifest elements to the dynamic
+ * import tables of this BundleLoader. Duplicate packages are checked and
+ * not added again.
+ * @param packages the DynamicImport-Package elements to add.
+ */
+ public final synchronized void addDynamicImportPackage(ManifestElement[] packages) {
+ if (packages == null)
+ return;
+ List<String> dynamicImports = new ArrayList<String>(packages.length);
+ List<ImportPackageSpecification> dynamicImportSpecs = new ArrayList<ImportPackageSpecification>(packages.length);
+ for (ManifestElement dynamicImportElement : packages) {
+ String[] names = dynamicImportElement.getValueComponents();
+ for (String name : names)
+ dynamicImports.add(name);
+ StateBuilder.addImportPackages(dynamicImportElement, dynamicImportSpecs, 2, true);
+ }
+ if (dynamicImports.size() > 0) {
+ addDynamicImportPackage(dynamicImports.toArray(new String[dynamicImports.size()]));
+ BundleDescription revision = getLoaderProxy().getBundleDescription();
+ State state = revision.getContainingState();
+ state.addDynamicImportPackages(revision, dynamicImportSpecs.toArray(new ImportPackageSpecification[dynamicImportSpecs.size()]));
+ }
+ }
+
+ synchronized public void attachFragment(BundleFragment fragment) throws BundleException {
+ ExportPackageDescription[] exports = proxy.getBundleDescription().getSelectedExports();
+ if (classloader == null) {
+ initializeExports(exports, exportedPackages);
+ return;
+ }
+ String[] classpath = fragment.getBundleData().getClassPath();
+ if (classpath != null)
+ classloader.attachFragment(fragment.getBundleData(), fragment.getProtectionDomain(), classpath);
+ initializeExports(exports, exportedPackages);
+ }
+
+ /*
+ * Finds a packagesource that is either imported or required from another bundle.
+ * This will not include an local package source
+ */
+ private PackageSource findSource(String pkgName) {
+ if (pkgName == null)
+ return null;
+ PackageSource result = findImportedSource(pkgName, null);
+ if (result != null)
+ return result;
+ // Note that dynamic imports are not checked to avoid aggressive wiring (bug 105779)
+ return findRequiredSource(pkgName, null);
+ }
+
+ private PackageSource findImportedSource(String pkgName, KeyedHashSet visited) {
+ KeyedHashSet imports = getImportedSources(visited);
+ if (imports == null)
+ return null;
+ synchronized (imports) {
+ return (PackageSource) imports.getByKey(pkgName);
+ }
+ }
+
+ private PackageSource findDynamicSource(String pkgName) {
+ if (isDynamicallyImported(pkgName)) {
+ ExportPackageDescription exportPackage = bundle.getFramework().getAdaptor().getState().linkDynamicImport(proxy.getBundleDescription(), pkgName);
+ if (exportPackage != null) {
+ PackageSource source = createExportPackageSource(exportPackage, null);
+ synchronized (this) {
+ if (importedSources == null)
+ importedSources = new KeyedHashSet(false);
+ }
+ synchronized (importedSources) {
+ importedSources.add(source);
+ }
+ return source;
+ }
+ }
+ return null;
+ }
+
+ private PackageSource findRequiredSource(String pkgName, KeyedHashSet visited) {
+ if (requiredBundles == null)
+ return null;
+ synchronized (requiredSources) {
+ PackageSource result = (PackageSource) requiredSources.getByKey(pkgName);
+ if (result != null)
+ return result.isNullSource() ? null : result;
+ }
+ if (visited == null)
+ visited = new KeyedHashSet(false);
+ visited.add(bundle); // always add ourselves so we do not recurse back to ourselves
+ List<PackageSource> result = new ArrayList<PackageSource>(3);
+ for (int i = 0; i < requiredBundles.length; i++) {
+ BundleLoader requiredLoader = requiredBundles[i].getBundleLoader();
+ requiredLoader.addExportedProvidersFor(proxy.getSymbolicName(), pkgName, result, visited);
+ }
+ // found some so cache the result for next time and return
+ PackageSource source;
+ if (result.size() == 0) {
+ // did not find it in our required bundles lets record the failure
+ // so we do not have to do the search again for this package.
+ source = NullPackageSource.getNullPackageSource(pkgName);
+ } else if (result.size() == 1) {
+ // if there is just one source, remember just the single source
+ source = result.get(0);
+ } else {
+ // if there was more than one source, build a multisource and cache that.
+ PackageSource[] srcs = result.toArray(new PackageSource[result.size()]);
+ source = createMultiSource(pkgName, srcs);
+ }
+ synchronized (requiredSources) {
+ requiredSources.add(source);
+ }
+ return source.isNullSource() ? null : source;
+ }
+
+ /*
+ * Gets the package source for the pkgName. This will include the local package source
+ * if the bundle exports the package. This is used to compare the PackageSource of a
+ * package from two different bundles.
+ */
+ public final PackageSource getPackageSource(String pkgName) {
+ PackageSource result = findSource(pkgName);
+ if (!isExportedPackage(pkgName))
+ return result;
+ // if the package is exported then we need to get the local source
+ PackageSource localSource = proxy.getPackageSource(pkgName);
+ if (result == null)
+ return localSource;
+ if (localSource == null)
+ return result;
+ return createMultiSource(pkgName, new PackageSource[] {result, localSource});
+ }
+
+ private ClassLoader getParentPrivileged(final BundleClassLoader bcl) {
+ if (System.getSecurityManager() == null)
+ return bcl.getParent();
+
+ return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
+ public ClassLoader run() {
+ return bcl.getParent();
+ }
+ });
+ }
+
+ static final class ClassContext extends SecurityManager {
+ // need to make this method public
+ public Class<?>[] getClassContext() {
+ return super.getClassContext();
+ }
+ }
+
+ static public void closeBundleLoader(BundleLoaderProxy proxy) {
+ if (proxy == null)
+ return;
+ // First close the BundleLoader
+ BundleLoader loader = proxy.getBasicBundleLoader();
+ if (loader != null)
+ loader.close();
+ proxy.setStale();
+ // if proxy is not null then make sure to unset user object
+ // associated with the proxy in the state
+ BundleDescription description = proxy.getBundleDescription();
+ // must set it back to the bundle object; not null
+ // need to make sure the user object is a BundleReference
+ description.setUserObject(proxy.getBundleData());
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/BundleLoaderProxy.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/BundleLoaderProxy.java
new file mode 100644
index 000000000..a9612a91b
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/BundleLoaderProxy.java
@@ -0,0 +1,247 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.loader;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.List;
+import org.eclipse.osgi.framework.adaptor.BundleData;
+import org.eclipse.osgi.framework.internal.core.*;
+import org.eclipse.osgi.framework.internal.core.Constants;
+import org.eclipse.osgi.framework.util.KeyedHashSet;
+import org.eclipse.osgi.framework.util.SecureAction;
+import org.eclipse.osgi.service.resolver.BundleDescription;
+import org.eclipse.osgi.service.resolver.ExportPackageDescription;
+import org.osgi.framework.*;
+import org.osgi.service.packageadmin.RequiredBundle;
+
+/*
+ * The BundleLoaderProxy proxies a BundleLoader object for a Bundle. This
+ * allows for a Bundle's depedencies to be linked without forcing the
+ * creating of the BundleLoader or BundleClassLoader objects. This class
+ * keeps track of the depedencies between the bundles installed in the
+ * Framework.
+ */
+public class BundleLoaderProxy implements RequiredBundle, BundleReference {
+ static SecureAction secureAction = AccessController.doPrivileged(SecureAction.createSecureAction());
+ // The BundleLoader that this BundleLoaderProxy is managing
+ private BundleLoader loader;
+ // The Bundle that this BundleLoaderProxy is for
+ final private BundleHost bundle;
+ // the BundleDescription for the Bundle
+ final private BundleDescription description;
+ // the BundleData for the bundle revision
+ final private BundleData data;
+ // Indicates if this BundleLoaderProxy is stale;
+ // this is true when the bundle is updated or uninstalled.
+ private boolean stale = false;
+ // cached of package sources for the bundle
+ final private KeyedHashSet pkgSources;
+
+ public BundleLoaderProxy(BundleHost bundle, BundleDescription description) {
+ this.bundle = bundle;
+ this.description = description;
+ this.pkgSources = new KeyedHashSet(false);
+ this.data = bundle.getBundleData();
+ }
+
+ public BundleLoader getBundleLoader() {
+ if (System.getSecurityManager() == null)
+ return getBundleLoader0();
+ return AccessController.doPrivileged(new PrivilegedAction<BundleLoader>() {
+ public BundleLoader run() {
+ return getBundleLoader0();
+ }
+ });
+ }
+
+ synchronized BundleLoader getBundleLoader0() {
+ if (loader != null)
+ return loader;
+ if (bundle.isResolved()) {
+ try {
+ if (bundle.getBundleId() == 0) // this is the system bundle
+ loader = new SystemBundleLoader(bundle, this);
+ else
+ loader = new BundleLoader(bundle, this);
+ } catch (BundleException e) {
+ bundle.getFramework().publishFrameworkEvent(FrameworkEvent.ERROR, bundle, e);
+ return null;
+ }
+ }
+ return loader;
+ }
+
+ public BundleLoader getBasicBundleLoader() {
+ return loader;
+ }
+
+ public AbstractBundle getBundleHost() {
+ return bundle;
+ }
+
+ void setStale() {
+ stale = true;
+ }
+
+ public boolean isStale() {
+ return stale;
+ }
+
+ public String toString() {
+ String symbolicName = bundle.getSymbolicName();
+ StringBuffer sb = new StringBuffer(symbolicName == null ? bundle.getBundleData().getLocation() : symbolicName);
+ sb.append("; ").append(Constants.BUNDLE_VERSION_ATTRIBUTE); //$NON-NLS-1$
+ sb.append("=\"").append(description.getVersion().toString()).append("\""); //$NON-NLS-1$//$NON-NLS-2$
+ return sb.toString();
+ }
+
+ public org.osgi.framework.Bundle getBundle() {
+ if (isStale())
+ return null;
+
+ return bundle;
+ }
+
+ public BundleData getBundleData() {
+ return data;
+ }
+
+ public Bundle[] getRequiringBundles() {
+ if (isStale())
+ return null;
+ // This is VERY slow; but never gets called in regular execution.
+ BundleDescription[] dependents = description.getDependents();
+ if (dependents == null || dependents.length == 0)
+ return new Bundle[0];
+ List<Bundle> result = new ArrayList<Bundle>(dependents.length);
+ for (int i = 0; i < dependents.length; i++)
+ addRequirers(dependents[i], result);
+ return result.toArray(new org.osgi.framework.Bundle[result.size()]);
+ }
+
+ void addRequirers(BundleDescription dependent, List<Bundle> result) {
+ if (dependent.getHost() != null) // don't look in fragments.
+ return;
+ BundleLoaderProxy dependentProxy = getBundleLoader().getLoaderProxy(dependent);
+ if (dependentProxy == null)
+ return; // bundle must have been uninstalled
+ if (result.contains(dependentProxy.bundle))
+ return; // prevent endless recusion
+ BundleLoader dependentLoader = dependentProxy.getBundleLoader();
+ BundleLoaderProxy[] requiredBundles = dependentLoader.requiredBundles;
+ int[] reexportTable = dependentLoader.reexportTable;
+ if (requiredBundles == null)
+ return;
+ int size = reexportTable == null ? 0 : reexportTable.length;
+ int reexportIndex = 0;
+ for (int i = 0; i < requiredBundles.length; i++) {
+ if (requiredBundles[i] == this) {
+ result.add(dependentProxy.bundle);
+ if (reexportIndex < size && reexportTable[reexportIndex] == i) {
+ reexportIndex++;
+ BundleDescription[] dependents = dependent.getDependents();
+ if (dependents == null)
+ return;
+ for (int j = 0; j < dependents.length; j++)
+ dependentProxy.addRequirers(dependents[j], result);
+ }
+ return;
+ }
+ }
+ return;
+ }
+
+ public String getSymbolicName() {
+ return description.getSymbolicName();
+ }
+
+ public Version getVersion() {
+ return description.getVersion();
+ }
+
+ public boolean isRemovalPending() {
+ return description.isRemovalPending();
+ }
+
+ public BundleDescription getBundleDescription() {
+ return description;
+ }
+
+ PackageSource getPackageSource(String pkgName) {
+ // getByKey is called outside of a synch block because we really do not
+ // care too much of duplicates getting created. Only the first one will
+ // successfully get stored into pkgSources
+ PackageSource pkgSource = (PackageSource) pkgSources.getByKey(pkgName);
+ if (pkgSource == null) {
+ pkgSource = new SingleSourcePackage(pkgName, this);
+ synchronized (pkgSources) {
+ pkgSources.add(pkgSource);
+ }
+ }
+ return pkgSource;
+ }
+
+ public boolean inUse() {
+ return (description.getDependents().length > 0);
+ }
+
+ boolean forceSourceCreation(ExportPackageDescription export) {
+ boolean strict = Constants.STRICT_MODE.equals(secureAction.getProperty(Constants.OSGI_RESOLVER_MODE));
+ return (export.getDirective(Constants.INCLUDE_DIRECTIVE) != null) || (export.getDirective(Constants.EXCLUDE_DIRECTIVE) != null) || (strict && export.getDirective(Constants.FRIENDS_DIRECTIVE) != null);
+ }
+
+ // creates a PackageSource from an ExportPackageDescription. This is called when initializing
+ // a BundleLoader to ensure that the proper PackageSource gets created and used for
+ // filtered and reexport packages. The storeSource flag is used by initialize to indicate
+ // that the source for special case package sources (filtered or re-exported should be stored
+ // in the cache. if this flag is set then a normal SinglePackageSource will not be created
+ // (i.e. it will be created lazily)
+ public PackageSource createPackageSource(ExportPackageDescription export, boolean storeSource) {
+ PackageSource pkgSource = null;
+
+ // check to see if it is a filtered export
+ String includes = (String) export.getDirective(Constants.INCLUDE_DIRECTIVE);
+ String excludes = (String) export.getDirective(Constants.EXCLUDE_DIRECTIVE);
+ String[] friends = (String[]) export.getDirective(Constants.FRIENDS_DIRECTIVE);
+ if (friends != null) {
+ boolean strict = Constants.STRICT_MODE.equals(secureAction.getProperty(Constants.OSGI_RESOLVER_MODE));
+ if (!strict)
+ friends = null; // do not pay attention to friends if not in strict mode
+ }
+ if (includes != null || excludes != null || friends != null) {
+ pkgSource = new FilteredSourcePackage(export.getName(), this, includes, excludes, friends);
+ }
+
+ if (storeSource) {
+ // if the package source is not null then store the source only if it is not already present;
+ // getByKey is called outside of a synch block because we really do not
+ // care too much of duplicates getting created. Only the first one will
+ // successfully get stored into pkgSources
+ if (pkgSource != null && pkgSources.getByKey(export.getName()) == null)
+ synchronized (pkgSources) {
+ pkgSources.add(pkgSource);
+ }
+ } else {
+ // we are not storing the special case sources, but pkgSource == null this means this
+ // is a normal package source; get it and return it.
+ if (pkgSource == null) {
+ pkgSource = getPackageSource(export.getName());
+ // the first export cached may not be a simple single source like we need.
+ if (pkgSource.getClass() != SingleSourcePackage.class)
+ return new SingleSourcePackage(export.getName(), this);
+ }
+ }
+
+ return pkgSource;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/FilteredSourcePackage.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/FilteredSourcePackage.java
new file mode 100644
index 000000000..287f9eb7a
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/FilteredSourcePackage.java
@@ -0,0 +1,112 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.loader;
+
+import java.net.URL;
+import java.util.*;
+import org.eclipse.osgi.util.ManifestElement;
+
+public class FilteredSourcePackage extends SingleSourcePackage {
+ private static final char ALL = '*';
+ String[] includes;
+ String[] excludes;
+ String[] friends;
+
+ public FilteredSourcePackage(String name, BundleLoaderProxy supplier, String includes, String excludes, String[] friends) {
+ super(name, supplier);
+ if (includes != null)
+ this.includes = ManifestElement.getArrayFromList(includes);
+ if (excludes != null)
+ this.excludes = ManifestElement.getArrayFromList(excludes);
+ this.friends = friends;
+ }
+
+ public boolean isFriend(String symbolicName) {
+ if (friends == null)
+ return true;
+ for (int i = 0; i < friends.length; i++)
+ if (friends[i].equals(symbolicName))
+ return true;
+ return false;
+ }
+
+ public URL getResource(String name) {
+ if (isFiltered(name, getId()))
+ return null;
+ return super.getResource(name);
+ }
+
+ public Enumeration<URL> getResources(String name) {
+ if (isFiltered(name, getId()))
+ return null;
+ return super.getResources(name);
+ }
+
+ public Class<?> loadClass(String name) throws ClassNotFoundException {
+ if (isFiltered(name, getId()))
+ return null;
+ return super.loadClass(name);
+ }
+
+ private boolean isFiltered(String name, String pkgName) {
+ String lastName = getName(name, pkgName);
+ return !isIncluded(lastName) || isExcluded(lastName);
+ }
+
+ private String getName(String name, String pkgName) {
+ if (!BundleLoader.DEFAULT_PACKAGE.equals(pkgName) && pkgName.length() + 1 <= name.length())
+ return name.substring(pkgName.length() + 1);
+ return name;
+ }
+
+ private boolean isIncluded(String name) {
+ if (includes == null)
+ return true;
+ return isInList(name, includes);
+ }
+
+ private boolean isExcluded(String name) {
+ if (excludes == null)
+ return false;
+ return isInList(name, excludes);
+ }
+
+ private boolean isInList(String name, String[] list) {
+ for (int i = 0; i < list.length; i++) {
+ int len = list[i].length();
+ if (len == 0)
+ continue;
+ if (list[i].charAt(0) == ALL && len == 1)
+ return true; // handles "*" wild card
+ if (list[i].charAt(len - 1) == ALL)
+ if (name.startsWith(list[i].substring(0, len - 1)))
+ return true;
+ if (name.equals(list[i]))
+ return true;
+
+ }
+ return false;
+ }
+
+ @Override
+ public Collection<String> listResources(String path, String filePattern) {
+ Collection<String> result = super.listResources(path, filePattern);
+ for (Iterator<String> resources = result.iterator(); resources.hasNext();) {
+ String resource = resources.next();
+ int lastSlash = resource.lastIndexOf('/');
+ String fileName = lastSlash >= 0 ? resource.substring(lastSlash + 1) : resource;
+ if (!isIncluded(fileName) || isExcluded(fileName))
+ resources.remove();
+ }
+ return result;
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/MultiSourcePackage.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/MultiSourcePackage.java
new file mode 100644
index 000000000..83eefc6c5
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/MultiSourcePackage.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.loader;
+
+import java.net.URL;
+import java.util.*;
+
+public class MultiSourcePackage extends PackageSource {
+ SingleSourcePackage[] suppliers;
+
+ MultiSourcePackage(String id, SingleSourcePackage[] suppliers) {
+ super(id);
+ this.suppliers = suppliers;
+ }
+
+ public SingleSourcePackage[] getSuppliers() {
+ return suppliers;
+ }
+
+ public Class<?> loadClass(String name) throws ClassNotFoundException {
+ Class<?> result = null;
+ for (int i = 0; i < suppliers.length; i++) {
+ result = suppliers[i].loadClass(name);
+ if (result != null)
+ return result;
+ }
+ return result;
+ }
+
+ public URL getResource(String name) {
+ URL result = null;
+ for (int i = 0; i < suppliers.length; i++) {
+ result = suppliers[i].getResource(name);
+ if (result != null)
+ return result;
+ }
+ return result;
+ }
+
+ public Enumeration<URL> getResources(String name) {
+ Enumeration<URL> results = null;
+ for (int i = 0; i < suppliers.length; i++)
+ results = BundleLoader.compoundEnumerations(results, suppliers[i].getResources(name));
+ return results;
+ }
+
+ @Override
+ public Collection<String> listResources(String path, String filePattern) {
+ List<String> result = new ArrayList<String>();
+ for (SingleSourcePackage source : suppliers) {
+ Collection<String> sourceResources = source.listResources(path, filePattern);
+ for (String resource : sourceResources) {
+ if (!result.contains(resource))
+ result.add(resource);
+ }
+ }
+ return result;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/NullPackageSource.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/NullPackageSource.java
new file mode 100644
index 000000000..796432ba8
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/NullPackageSource.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.loader;
+
+import java.net.URL;
+import java.util.*;
+import org.eclipse.osgi.framework.util.KeyedHashSet;
+
+/**
+ * This class is used to optimize finding provided-packages for a bundle.
+ * If the package cannot be found in a list of required bundles then this class
+ * is used to cache a null package source so that the search does not need to
+ * be done again.
+ */
+public class NullPackageSource extends PackageSource {
+ static KeyedHashSet sources;
+
+ private NullPackageSource(String name) {
+ super(name);
+ }
+
+ public SingleSourcePackage[] getSuppliers() {
+ return null;
+ }
+
+ public boolean isNullSource() {
+ return true;
+ }
+
+ public String toString() {
+ return id + " -> null"; //$NON-NLS-1$
+ }
+
+ public Class<?> loadClass(String name) {
+ return null;
+ }
+
+ public URL getResource(String name) {
+ return null;
+ }
+
+ public Enumeration<URL> getResources(String name) {
+ return null;
+ }
+
+ public static synchronized NullPackageSource getNullPackageSource(String name) {
+ if (sources == null)
+ sources = new KeyedHashSet();
+ NullPackageSource result = (NullPackageSource) sources.getByKey(name);
+ if (result != null)
+ return result;
+ result = new NullPackageSource(name);
+ sources.add(result);
+ return result;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public List<String> listResources(String path, String filePattern) {
+ return Collections.EMPTY_LIST;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/PackageSource.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/PackageSource.java
new file mode 100644
index 000000000..0f2d98835
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/PackageSource.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.loader;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Enumeration;
+import org.eclipse.osgi.framework.util.KeyedElement;
+
+public abstract class PackageSource implements KeyedElement {
+ protected String id;
+
+ public PackageSource(String id) {
+ // others depend on the id being interned; see SingleSourcePackage.equals
+ this.id = id.intern();
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public abstract SingleSourcePackage[] getSuppliers();
+
+ public boolean compare(KeyedElement other) {
+ return id.equals(((PackageSource) other).getId());
+ }
+
+ public int getKeyHashCode() {
+ return id.hashCode();
+ }
+
+ public Object getKey() {
+ return id;
+ }
+
+ public boolean isNullSource() {
+ return false;
+ }
+
+ public boolean isFriend(String symbolicName) {
+ return true;
+ }
+
+ public abstract Class<?> loadClass(String name) throws ClassNotFoundException;
+
+ public abstract URL getResource(String name);
+
+ public abstract Enumeration<URL> getResources(String name) throws IOException;
+
+ //TODO See how this relates with FilteredSourcePackage. Overwriting or doing a double dispatch might be good.
+ // This is intentionally lenient; we don't force all suppliers to match (only one)
+ // it is better to get class cast exceptions in split package cases than miss an event
+ public boolean hasCommonSource(PackageSource other) {
+ if (other == null)
+ return false;
+ if (this == other)
+ return true;
+ SingleSourcePackage[] suppliers1 = getSuppliers();
+ SingleSourcePackage[] suppliers2 = other.getSuppliers();
+ if (suppliers1 == null || suppliers2 == null)
+ return false;
+ // This will return true if the specified source has at least one
+ // of the suppliers of this source.
+ for (int i = 0; i < suppliers1.length; i++)
+ for (int j = 0; j < suppliers2.length; j++)
+ if (suppliers2[j].equals(suppliers1[i]))
+ return true;
+ return false;
+ }
+
+ public abstract Collection<String> listResources(String path, String filePattern);
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/SingleSourcePackage.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/SingleSourcePackage.java
new file mode 100644
index 000000000..533c99676
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/SingleSourcePackage.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.loader;
+
+import java.net.URL;
+import java.util.Collection;
+import java.util.Enumeration;
+import org.eclipse.osgi.framework.adaptor.BundleClassLoader;
+
+public class SingleSourcePackage extends PackageSource {
+ BundleLoaderProxy supplier;
+
+ public SingleSourcePackage(String id, BundleLoaderProxy supplier) {
+ super(id);
+ this.supplier = supplier;
+ }
+
+ public SingleSourcePackage[] getSuppliers() {
+ return new SingleSourcePackage[] {this};
+ }
+
+ public String toString() {
+ return id + " -> " + supplier; //$NON-NLS-1$
+ }
+
+ public Class<?> loadClass(String name) throws ClassNotFoundException {
+ return supplier.getBundleLoader().findLocalClass(name);
+ }
+
+ public URL getResource(String name) {
+ return supplier.getBundleLoader().findLocalResource(name);
+ }
+
+ public Enumeration<URL> getResources(String name) {
+ return supplier.getBundleLoader().findLocalResources(name);
+ }
+
+ public boolean equals(Object source) {
+ if (this == source)
+ return true;
+ if (!(source instanceof SingleSourcePackage))
+ return false;
+ SingleSourcePackage singleSource = (SingleSourcePackage) source;
+ // we do an == test on id because the id is interned in the constructor of PackageSource
+ return supplier == singleSource.supplier && id == singleSource.getId();
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + id.hashCode();
+ result = prime * result + supplier.hashCode();
+ return result;
+ }
+
+ @Override
+ public Collection<String> listResources(String path, String filePattern) {
+ BundleClassLoader bcl = supplier.getBundleLoader().createClassLoader();
+ return bcl.listLocalResources(path, filePattern, 0);
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/SystemBundleLoader.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/SystemBundleLoader.java
new file mode 100644
index 000000000..252fdb2cb
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/SystemBundleLoader.java
@@ -0,0 +1,277 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.loader;
+
+import java.io.IOException;
+import java.net.URL;
+import java.security.ProtectionDomain;
+import java.util.*;
+import org.eclipse.osgi.framework.adaptor.*;
+import org.eclipse.osgi.framework.internal.core.BundleFragment;
+import org.eclipse.osgi.framework.internal.core.BundleHost;
+import org.eclipse.osgi.service.resolver.ExportPackageDescription;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.wiring.BundleWiring;
+
+/**
+ * The System Bundle's BundleLoader. This BundleLoader is used by ImportClassLoaders
+ * to load a resource that is exported by the System Bundle.
+ */
+public class SystemBundleLoader extends BundleLoader {
+ public static final String EQUINOX_EE = "x-equinox-ee"; //$NON-NLS-1$
+ final ClassLoader classLoader;
+ private final Set<String> eePackages;
+ private final Set<String> extPackages;
+ private final ClassLoader extClassLoader;
+
+ /**
+ * @param bundle The system bundle.
+ * @param proxy The BundleLoaderProxy for the system bundle
+ * @throws BundleException On any error.
+ */
+ protected SystemBundleLoader(BundleHost bundle, BundleLoaderProxy proxy) throws BundleException {
+ super(bundle, proxy);
+ ExportPackageDescription[] exports = proxy.getBundleDescription().getSelectedExports();
+ if (exports == null || exports.length == 0)
+ eePackages = null;
+ else {
+ eePackages = new HashSet<String>(exports.length);
+ for (int i = 0; i < exports.length; i++)
+ if (((Integer) exports[i].getDirective(EQUINOX_EE)).intValue() >= 0)
+ eePackages.add(exports[i].getName());
+ }
+ this.classLoader = getClass().getClassLoader();
+ extPackages = new HashSet<String>(0); // not common; start with 0
+ BundleFragment[] fragments = bundle.getFragments();
+ if (fragments != null)
+ for (int i = 0; i < fragments.length; i++)
+ addExtPackages(fragments[i]);
+ ClassLoader extCL = ClassLoader.getSystemClassLoader();
+ if (extCL == null)
+ extClassLoader = null;
+ else {
+ while (extCL.getParent() != null)
+ extCL = extCL.getParent();
+ // make sure extCL is not already on the parent chain of the system classloader
+ boolean found = false;
+ ClassLoader systemExtCL = this.classLoader;
+ while (systemExtCL.getParent() != null && !found) {
+ if (systemExtCL.getParent() == extCL)
+ found = true;
+ else
+ systemExtCL = systemExtCL.getParent();
+ }
+ extClassLoader = found ? null : extCL;
+ }
+ }
+
+ private void addExtPackages(BundleFragment fragment) {
+ if ((fragment.getBundleData().getType() & BundleData.TYPE_EXTCLASSPATH_EXTENSION) == 0)
+ return;
+ ExportPackageDescription[] extExports = fragment.getBundleDescription().getExportPackages();
+ for (int j = 0; j < extExports.length; j++)
+ extPackages.add(extExports[j].getName());
+ }
+
+ synchronized public void attachFragment(BundleFragment fragment) throws BundleException {
+ super.attachFragment(fragment);
+ synchronized (extPackages) {
+ addExtPackages(fragment);
+ }
+ }
+
+ /**
+ * The ClassLoader that loads OSGi framework classes is used to find the class.
+ * This method never gets called because there is no BundleClassLoader for the framework.
+ */
+ public Class<?> findClass(String name) throws ClassNotFoundException {
+ Class<?> result = findLocalClass(name);
+ if (result == null)
+ throw new ClassNotFoundException(name);
+ return result;
+ }
+
+ /**
+ * This method will always return null.
+ * This method never gets called because there is no BundleClassLoader for the framework.
+ */
+ public String findLibrary(String name) {
+ return null;
+ }
+
+ /**
+ * The ClassLoader that loads OSGi framework classes is used to find the class.
+ */
+ Class<?> findLocalClass(String name) {
+ try {
+ return classLoader.loadClass(name);
+ } catch (ClassNotFoundException e) {
+ if (extClassLoader != null)
+ synchronized (extPackages) {
+ if (extPackages.size() > 0 && extPackages.contains(BundleLoader.getPackageName(name)))
+ try {
+ return extClassLoader.loadClass(name);
+ } catch (ClassNotFoundException e2) {
+ return null;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * The ClassLoader that loads OSGi framework classes is used to find the resource.
+ */
+ URL findLocalResource(String name) {
+ URL result = classLoader.getResource(name);
+ if (result == null && extClassLoader != null)
+ synchronized (extPackages) {
+ if (extPackages.size() > 0 && extPackages.contains(BundleLoader.getResourcePackageName(name)))
+ result = extClassLoader.getResource(name);
+ }
+ return result;
+ }
+
+ /**
+ * The ClassLoader that loads OSGi framework classes is used to find the resource.
+ */
+ Enumeration<URL> findLocalResources(String name) {
+ Enumeration<URL> result = null;
+ try {
+ result = classLoader.getResources(name);
+ } catch (IOException e) {
+ // do nothing
+ }
+ if ((result == null || !result.hasMoreElements()) && extClassLoader != null)
+ synchronized (extPackages) {
+ if (extPackages.size() > 0 && extPackages.contains(BundleLoader.getResourcePackageName(name)))
+ try {
+ result = extClassLoader.getResources(name);
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+ return result;
+ }
+
+ /**
+ * The ClassLoader that loads OSGi framework classes is used to find the resource.
+ * This method never gets called because there is no BundleClassLoader for the framework.
+ */
+ public URL findResource(String name) {
+ return findLocalResource(name);
+ }
+
+ /**
+ * The ClassLoader that loads OSGi framework classes is used to find the resource.
+ * This method never gets called because there is no BundleClassLoader for the framework.
+ * @throws IOException
+ */
+ public Enumeration<URL> findResources(String name) throws IOException {
+ return findLocalResources(name);
+ }
+
+ /**
+ * Do nothing on a close.
+ */
+ protected void close() {
+ // Do nothing.
+ }
+
+ public boolean isEEPackage(String pkgName) {
+ return eePackages.contains(pkgName);
+ }
+
+ BundleClassLoader createBCL(BundleProtectionDomain pd, String[] cp) {
+ return new BundleClassLoader() {
+
+ public Bundle getBundle() {
+ return SystemBundleLoader.this.getBundle();
+ }
+
+ public Class<?> loadClass(String name) throws ClassNotFoundException {
+ return SystemBundleLoader.this.loadClass(name);
+ }
+
+ public void initialize() {
+ // nothing
+ }
+
+ /**
+ * @throws IOException
+ */
+ public Enumeration<URL> getResources(String name) throws IOException {
+ return findLocalResources(name);
+ }
+
+ public URL getResource(String name) {
+ return SystemBundleLoader.this.findLocalResource(name);
+ }
+
+ public ClassLoader getParent() {
+ return SystemBundleLoader.this.classLoader.getParent();
+ }
+
+ public ClassLoaderDelegate getDelegate() {
+ return SystemBundleLoader.this;
+ }
+
+ public Enumeration<URL> findLocalResources(String resource) {
+ return SystemBundleLoader.this.findLocalResources(resource);
+ }
+
+ public URL findLocalResource(String resource) {
+ return getResource(resource);
+ }
+
+ /**
+ * @throws ClassNotFoundException
+ */
+ public Class<?> findLocalClass(String classname) throws ClassNotFoundException {
+ return SystemBundleLoader.this.findLocalClass(classname);
+ }
+
+ public void close() {
+ // nothing
+ }
+
+ public void attachFragment(BundleData bundledata, ProtectionDomain domain, String[] classpath) {
+ // nothing
+ }
+
+ public List<URL> findEntries(String path, String filePattern, int options) {
+ Bundle systemBundle = SystemBundleLoader.this.getBundle();
+ boolean recurse = (options & BundleWiring.FINDENTRIES_RECURSE) != 0;
+ @SuppressWarnings("unchecked")
+ List<URL> result = Collections.EMPTY_LIST;
+ Enumeration<URL> entries = systemBundle.findEntries(path, filePattern, recurse);
+ if (entries != null) {
+ result = new ArrayList<URL>();
+ while (entries.hasMoreElements())
+ result.add(entries.nextElement());
+ }
+ return Collections.unmodifiableList(result);
+ }
+
+ @SuppressWarnings("unchecked")
+ public Collection<String> listResources(String path, String filePattern, int options) {
+ return Collections.EMPTY_LIST;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Collection<String> listLocalResources(String path, String filePattern, int options) {
+ return Collections.EMPTY_LIST;
+ }
+ };
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/buddy/DependentPolicy.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/buddy/DependentPolicy.java
new file mode 100644
index 000000000..74121aeaa
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/buddy/DependentPolicy.java
@@ -0,0 +1,119 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.loader.buddy;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.*;
+import org.eclipse.osgi.internal.loader.BundleLoader;
+import org.eclipse.osgi.internal.loader.BundleLoaderProxy;
+import org.eclipse.osgi.service.resolver.BundleDescription;
+
+/**
+ * DependentPolicy is an implementation of a buddy policy.
+ * It is responsible for looking up a class in the dependents of the bundle
+ * to which this policy is attached to.
+ */
+public class DependentPolicy implements IBuddyPolicy {
+ BundleLoader buddyRequester;
+ int lastDependentOfAdded = -1; //remember the index of the bundle for which we last added the dependent
+ List<BundleDescription> allDependents = null; //the list of all dependents known so far
+
+ public DependentPolicy(BundleLoader requester) {
+ buddyRequester = requester;
+
+ //Initialize with the first level of dependent the list
+ allDependents = new ArrayList<BundleDescription>();
+ basicAddImmediateDependents(buddyRequester.getBundle().getBundleDescription());
+ //If there is no dependent, reset to null
+ if (allDependents.size() == 0)
+ allDependents = null;
+ }
+
+ public Class<?> loadClass(String name) {
+ if (allDependents == null)
+ return null;
+
+ Class<?> result = null;
+ //size may change, so we must check it every time
+ for (int i = 0; i < allDependents.size() && result == null; i++) {
+ BundleDescription searchedBundle = allDependents.get(i);
+ try {
+ BundleLoaderProxy proxy = buddyRequester.getLoaderProxy(searchedBundle);
+ if (proxy == null)
+ continue;
+ result = proxy.getBundleLoader().findClass(name);
+ } catch (ClassNotFoundException e) {
+ if (result == null)
+ addDependent(i, searchedBundle);
+ }
+ }
+ return result;
+ }
+
+ private synchronized void addDependent(int i, BundleDescription searchedBundle) {
+ if (i > lastDependentOfAdded) {
+ lastDependentOfAdded = i;
+ basicAddImmediateDependents(searchedBundle);
+ }
+ }
+
+ public URL loadResource(String name) {
+ if (allDependents == null)
+ return null;
+
+ URL result = null;
+ //size may change, so we must check it every time
+ for (int i = 0; i < allDependents.size() && result == null; i++) {
+ BundleDescription searchedBundle = allDependents.get(i);
+ BundleLoaderProxy proxy = buddyRequester.getLoaderProxy(searchedBundle);
+ if (proxy == null)
+ continue;
+ result = proxy.getBundleLoader().findResource(name);
+ if (result == null) {
+ addDependent(i, searchedBundle);
+ }
+ }
+ return result;
+ }
+
+ public Enumeration<URL> loadResources(String name) {
+ if (allDependents == null)
+ return null;
+
+ Enumeration<URL> results = null;
+ //size may change, so we must check it every time
+ for (int i = 0; i < allDependents.size(); i++) {
+ BundleDescription searchedBundle = allDependents.get(i);
+ try {
+ BundleLoaderProxy proxy = buddyRequester.getLoaderProxy(searchedBundle);
+ if (proxy == null)
+ continue;
+ results = BundleLoader.compoundEnumerations(results, proxy.getBundleLoader().findResources(name));
+ addDependent(i, searchedBundle);
+ } catch (IOException e) {
+ //Ignore and keep looking
+ }
+ }
+ return results;
+ }
+
+ private void basicAddImmediateDependents(BundleDescription root) {
+ BundleDescription[] dependents = root.getDependents();
+ for (int i = 0; i < dependents.length; i++) {
+ BundleDescription toAdd = dependents[i];
+ if (toAdd.getHost() == null && !allDependents.contains(toAdd)) {
+ allDependents.add(toAdd);
+ }
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/buddy/GlobalPolicy.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/buddy/GlobalPolicy.java
new file mode 100644
index 000000000..1985ff87a
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/buddy/GlobalPolicy.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.loader.buddy;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Enumeration;
+import org.eclipse.osgi.internal.loader.BundleLoader;
+import org.osgi.service.packageadmin.ExportedPackage;
+import org.osgi.service.packageadmin.PackageAdmin;
+
+/**
+ * Global policy is an implementation of a buddy policy. It is responsible
+ * for looking up a class within the global set of exported classes. If multiple
+ * version of the same package are exported in the system, the exported package
+ * with the highest version will be returned.
+ */
+public class GlobalPolicy implements IBuddyPolicy {
+ private PackageAdmin admin;
+
+ public GlobalPolicy(PackageAdmin admin) {
+ this.admin = admin;
+ }
+
+ public Class<?> loadClass(String name) {
+ ExportedPackage pkg = admin.getExportedPackage(BundleLoader.getPackageName(name));
+ if (pkg == null)
+ return null;
+ try {
+ return pkg.getExportingBundle().loadClass(name);
+ } catch (ClassNotFoundException e) {
+ return null;
+ }
+ }
+
+ public URL loadResource(String name) {
+ //get all exported packages that match the resource's package
+ ExportedPackage pkg = admin.getExportedPackage(BundleLoader.getResourcePackageName(name));
+ if (pkg == null)
+ return null;
+ return pkg.getExportingBundle().getResource(name);
+ }
+
+ public Enumeration<URL> loadResources(String name) {
+ //get all exported packages that match the resource's package
+ ExportedPackage[] pkgs = admin.getExportedPackages(BundleLoader.getResourcePackageName(name));
+ if (pkgs == null || pkgs.length == 0)
+ return null;
+
+ //get all matching resources for each package
+ Enumeration<URL> results = null;
+ for (int i = 0; i < pkgs.length; i++) {
+ try {
+ results = BundleLoader.compoundEnumerations(results, pkgs[i].getExportingBundle().getResources(name));
+ } catch (IOException e) {
+ //ignore IO problems and try next package
+ }
+ }
+
+ return results;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/buddy/IBuddyPolicy.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/buddy/IBuddyPolicy.java
new file mode 100644
index 000000000..a4066eeea
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/buddy/IBuddyPolicy.java
@@ -0,0 +1,22 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.loader.buddy;
+
+import java.net.URL;
+import java.util.Enumeration;
+
+public interface IBuddyPolicy {
+ public Class<?> loadClass(String name);
+
+ public URL loadResource(String name);
+
+ public Enumeration<URL> loadResources(String name);
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/buddy/PolicyHandler.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/buddy/PolicyHandler.java
new file mode 100644
index 000000000..0795902e1
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/buddy/PolicyHandler.java
@@ -0,0 +1,219 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.loader.buddy;
+
+import java.net.URL;
+import java.util.*;
+import org.eclipse.osgi.framework.internal.core.Constants;
+import org.eclipse.osgi.internal.loader.BundleLoader;
+import org.osgi.framework.*;
+import org.osgi.service.packageadmin.PackageAdmin;
+
+public class PolicyHandler implements SynchronousBundleListener {
+ //Key for the framework buddies
+ private final static String DEPENDENT_POLICY = "dependent"; //$NON-NLS-1$
+ private final static String GLOBAL_POLICY = "global"; //$NON-NLS-1$
+ private final static String REGISTERED_POLICY = "registered"; //$NON-NLS-1$
+ private final static String APP_POLICY = "app"; //$NON-NLS-1$
+ private final static String EXT_POLICY = "ext"; //$NON-NLS-1$
+ private final static String BOOT_POLICY = "boot"; //$NON-NLS-1$
+ private final static String PARENT_POLICY = "parent"; //$NON-NLS-1$
+
+ //The loader to which this policy is attached.
+ private final BundleLoader policedLoader;
+ //List of the policies as well as cache for the one that have been created. The size of this array never changes over time. This is why the synchronization is not done when iterating over it.
+ private volatile Object[] policies = null;
+
+ //Support to cut class / resource loading cycles in the context of one thread. The contained object is a set of classname
+ private final ThreadLocal<Set<String>> beingLoaded;
+ private final PackageAdmin packageAdmin;
+
+ public PolicyHandler(BundleLoader loader, String buddyList, PackageAdmin packageAdmin) {
+ policedLoader = loader;
+ policies = getArrayFromList(buddyList);
+ beingLoaded = new ThreadLocal<Set<String>>();
+ this.packageAdmin = packageAdmin;
+ }
+
+ static Object[] getArrayFromList(String stringList) {
+ if (stringList == null || stringList.trim().equals("")) //$NON-NLS-1$
+ return null;
+ List<Object> list = new ArrayList<Object>();
+ StringTokenizer tokens = new StringTokenizer(stringList, ","); //$NON-NLS-1$
+ while (tokens.hasMoreTokens()) {
+ String token = tokens.nextToken().trim();
+ if (!token.equals("")) //$NON-NLS-1$
+ list.add(token);
+ }
+ return list.isEmpty() ? new Object[0] : list.toArray(new Object[list.size()]);
+ }
+
+ private IBuddyPolicy getPolicyImplementation(Object[] policiesSnapshot, int policyOrder) {
+ synchronized (policiesSnapshot) {
+ if (policyOrder >= policiesSnapshot.length)
+ return null;
+ if (policiesSnapshot[policyOrder] instanceof String) {
+ String buddyName = (String) policiesSnapshot[policyOrder];
+
+ if (REGISTERED_POLICY.equals(buddyName)) {
+ policiesSnapshot[policyOrder] = new RegisteredPolicy(policedLoader);
+ return (IBuddyPolicy) policiesSnapshot[policyOrder];
+ }
+ if (BOOT_POLICY.equals(buddyName)) {
+ policiesSnapshot[policyOrder] = SystemPolicy.getInstance(SystemPolicy.BOOT);
+ return (IBuddyPolicy) policiesSnapshot[policyOrder];
+ }
+ if (APP_POLICY.equals(buddyName)) {
+ policiesSnapshot[policyOrder] = SystemPolicy.getInstance(SystemPolicy.APP);
+ return (IBuddyPolicy) policiesSnapshot[policyOrder];
+ }
+ if (EXT_POLICY.equals(buddyName)) {
+ policiesSnapshot[policyOrder] = SystemPolicy.getInstance(SystemPolicy.EXT);
+ return (IBuddyPolicy) policiesSnapshot[policyOrder];
+ }
+ if (DEPENDENT_POLICY.equals(buddyName)) {
+ policiesSnapshot[policyOrder] = new DependentPolicy(policedLoader);
+ return (IBuddyPolicy) policiesSnapshot[policyOrder];
+ }
+ if (GLOBAL_POLICY.equals(buddyName)) {
+ policiesSnapshot[policyOrder] = new GlobalPolicy(packageAdmin);
+ return (IBuddyPolicy) policiesSnapshot[policyOrder];
+ }
+ if (PARENT_POLICY.equals(buddyName)) {
+ policiesSnapshot[policyOrder] = new SystemPolicy(policedLoader.getParentClassLoader());
+ return (IBuddyPolicy) policiesSnapshot[policyOrder];
+ }
+
+ // //Buddy policy can be provided by service implementations
+ // BundleContext fwkCtx = policedLoader.bundle.framework.systemBundle.context;
+ // ServiceReference[] matchingBuddies = null;
+ // try {
+ // matchingBuddies = fwkCtx.getAllServiceReferences(IBuddyPolicy.class.getName(), "buddyName=" + buddyName);
+ // } catch (InvalidSyntaxException e) {
+ // //The filter is valid
+ // }
+ // if (matchingBuddies == null)
+ // return new IBuddyPolicy() {
+ // public Class loadClass(String name) {
+ // return null;
+ // }
+ //
+ // public URL loadResource(String name) {
+ // return null;
+ // }
+ //
+ // public Enumeration loadResources(String name) {
+ // return null;
+ // }
+ // };
+ //
+ // //The policies loaded through service are not cached
+ // return ((IBuddyPolicy) fwkCtx.getService(matchingBuddies[0]));
+ }
+ return (IBuddyPolicy) policiesSnapshot[policyOrder];
+ }
+ }
+
+ public Class<?> doBuddyClassLoading(String name) {
+ if (startLoading(name) == false)
+ return null;
+
+ Class<?> result = null;
+ Object[] policiesSnapshot = policies;
+ int policyCount = (policiesSnapshot == null) ? 0 : policiesSnapshot.length;
+ for (int i = 0; i < policyCount && result == null; i++) {
+ IBuddyPolicy policy = getPolicyImplementation(policiesSnapshot, i);
+ if (policy != null)
+ result = policy.loadClass(name);
+ }
+ stopLoading(name);
+ return result;
+ }
+
+ public URL doBuddyResourceLoading(String name) {
+ if (startLoading(name) == false)
+ return null;
+
+ URL result = null;
+ Object[] policiesSnapshot = policies;
+ int policyCount = (policiesSnapshot == null) ? 0 : policiesSnapshot.length;
+ for (int i = 0; i < policyCount && result == null; i++) {
+ IBuddyPolicy policy = getPolicyImplementation(policiesSnapshot, i);
+ if (policy != null)
+ result = policy.loadResource(name);
+ }
+ stopLoading(name);
+ return result;
+ }
+
+ public Enumeration<URL> doBuddyResourcesLoading(String name) {
+ if (startLoading(name) == false)
+ return null;
+
+ List<URL> results = null;
+ Object[] policiesSnapshot = policies;
+ int policyCount = (policiesSnapshot == null) ? 0 : policiesSnapshot.length;
+ for (int i = 0; i < policyCount; i++) {
+ IBuddyPolicy policy = getPolicyImplementation(policiesSnapshot, i);
+ if (policy == null)
+ continue;
+ Enumeration<URL> result = policy.loadResources(name);
+ if (result != null) {
+ if (results == null)
+ results = new ArrayList<URL>(policyCount);
+ while (result.hasMoreElements()) {
+ URL url = result.nextElement();
+ if (!results.contains(url)) //only add if not already added
+ results.add(url);
+ }
+ }
+ }
+ stopLoading(name);
+ return results == null || results.isEmpty() ? null : Collections.enumeration(results);
+ }
+
+ private boolean startLoading(String name) {
+ Set<String> classesAndResources = beingLoaded.get();
+ if (classesAndResources != null && classesAndResources.contains(name))
+ return false;
+
+ if (classesAndResources == null) {
+ classesAndResources = new HashSet<String>(3);
+ beingLoaded.set(classesAndResources);
+ }
+ classesAndResources.add(name);
+ return true;
+ }
+
+ private void stopLoading(String name) {
+ beingLoaded.get().remove(name);
+ }
+
+ public void open(BundleContext context) {
+ context.addBundleListener(this);
+ }
+
+ public void close(BundleContext context) {
+ context.removeBundleListener(this);
+ }
+
+ public void bundleChanged(BundleEvent event) {
+ if ((event.getType() & (BundleEvent.RESOLVED | BundleEvent.UNRESOLVED)) == 0)
+ return;
+ // reinitialize the policies
+ try {
+ String list = policedLoader.getBundle().getBundleData().getManifest().get(Constants.BUDDY_LOADER);
+ policies = getArrayFromList(list);
+ } catch (BundleException e) {
+ //Ignore
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/buddy/RegisteredPolicy.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/buddy/RegisteredPolicy.java
new file mode 100644
index 000000000..bf59610c1
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/buddy/RegisteredPolicy.java
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.loader.buddy;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.Iterator;
+import org.eclipse.osgi.framework.internal.core.AbstractBundle;
+import org.eclipse.osgi.framework.internal.core.Constants;
+import org.eclipse.osgi.internal.loader.BundleLoader;
+import org.eclipse.osgi.internal.loader.BundleLoaderProxy;
+import org.eclipse.osgi.service.resolver.BundleDescription;
+import org.eclipse.osgi.util.ManifestElement;
+import org.osgi.framework.BundleException;
+
+/**
+ *Registered policy is an implementation of a buddy policy.
+ * It is responsible for looking up a class in the bundles (registrant) that declare interest in the bundle that require the buddy loading.
+ * Note that the registrants must have a direct dependency on the bundle needing buddy.
+ */
+public class RegisteredPolicy extends DependentPolicy {
+
+ public RegisteredPolicy(BundleLoader requester) {
+ super(requester);
+
+ //Filter the dependents;
+ if (allDependents == null)
+ return;
+
+ for (Iterator<BundleDescription> iter = allDependents.iterator(); iter.hasNext();) {
+ BundleLoaderProxy proxy = buddyRequester.getLoaderProxy(iter.next());
+ if (proxy == null)
+ iter.remove();
+
+ try {
+ String[] allContributions = ManifestElement.getArrayFromList(((AbstractBundle) proxy.getBundle()).getBundleData().getManifest().get(Constants.REGISTERED_POLICY));
+ if (allContributions == null) {
+ iter.remove();
+ continue;
+ }
+ boolean contributes = false;
+ for (int j = 0; j < allContributions.length && contributes == false; j++) {
+ if (allContributions[j].equals(buddyRequester.getBundle().getSymbolicName()))
+ contributes = true;
+ }
+ if (!contributes)
+ iter.remove();
+
+ } catch (BundleException e) {
+ iter.remove();
+ }
+ }
+
+ //After the filtering, if nothing is left then null out the variable for optimization
+ if (allDependents.size() == 0)
+ allDependents = null;
+ }
+
+ public Class<?> loadClass(String name) {
+ if (allDependents == null)
+ return null;
+
+ Class<?> result = null;
+ int size = allDependents.size();
+ for (int i = 0; i < size && result == null; i++) {
+ try {
+ BundleLoaderProxy proxy = buddyRequester.getLoaderProxy(allDependents.get(i));
+ if (proxy == null)
+ continue;
+ result = proxy.getBundleLoader().findClass(name);
+ } catch (ClassNotFoundException e) {
+ //Nothing to do, just keep looking
+ continue;
+ }
+ }
+ return result;
+ }
+
+ public URL loadResource(String name) {
+ if (allDependents == null)
+ return null;
+
+ URL result = null;
+ int size = allDependents.size();
+ for (int i = 0; i < size && result == null; i++) {
+ BundleLoaderProxy proxy = buddyRequester.getLoaderProxy(allDependents.get(i));
+ if (proxy == null)
+ continue;
+ result = proxy.getBundleLoader().findResource(name);
+ }
+ return result;
+ }
+
+ public Enumeration<URL> loadResources(String name) {
+ if (allDependents == null)
+ return null;
+
+ Enumeration<URL> results = null;
+ int size = allDependents.size();
+ for (int i = 0; i < size; i++) {
+ try {
+ BundleLoaderProxy proxy = buddyRequester.getLoaderProxy(allDependents.get(i));
+ if (proxy == null)
+ continue;
+ results = BundleLoader.compoundEnumerations(results, proxy.getBundleLoader().findResources(name));
+ } catch (IOException e) {
+ //Ignore and keep looking
+ }
+ }
+ return results;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/buddy/SystemPolicy.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/buddy/SystemPolicy.java
new file mode 100644
index 000000000..a72e6ac15
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/buddy/SystemPolicy.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.loader.buddy;
+
+import java.io.IOException;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Enumeration;
+
+public class SystemPolicy implements IBuddyPolicy {
+
+ private static class ParentClassLoader extends ClassLoader {
+ protected ParentClassLoader() {
+ super(Object.class.getClassLoader());
+ }
+ }
+
+ public static final byte BOOT = 0;
+ public static final byte EXT = 1;
+ public static final byte APP = 2;
+
+ private static SystemPolicy[] instances = new SystemPolicy[3];
+
+ private ClassLoader classLoader;
+
+ public static SystemPolicy getInstance(final byte type) {
+ if (instances[type] == null) {
+ instances[type] = new SystemPolicy();
+ instances[type].classLoader = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
+ public ClassLoader run() {
+ return createClassLoader(type);
+ }
+ });
+ }
+ return instances[type];
+ }
+
+ public SystemPolicy() {
+ //Nothing to do
+ }
+
+ public SystemPolicy(ClassLoader parent) {
+ classLoader = parent;
+ }
+
+ static ClassLoader createClassLoader(byte type) {
+ switch (type) {
+ case APP :
+ if (ClassLoader.getSystemClassLoader() != null)
+ return ClassLoader.getSystemClassLoader();
+ return new ParentClassLoader();
+
+ case BOOT :
+ return new ParentClassLoader();
+
+ case EXT :
+ if (ClassLoader.getSystemClassLoader() != null)
+ return ClassLoader.getSystemClassLoader().getParent();
+ return new ParentClassLoader();
+ }
+ return null;
+ }
+
+ public Class<?> loadClass(String name) {
+ try {
+ return classLoader.loadClass(name);
+ } catch (ClassNotFoundException e) {
+ return null;
+ }
+ }
+
+ public URL loadResource(String name) {
+ return classLoader.getResource(name);
+ }
+
+ public Enumeration<URL> loadResources(String name) {
+ try {
+ return classLoader.getResources(name);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/BundlePermissions.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/BundlePermissions.java
new file mode 100644
index 000000000..5ade23ff0
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/BundlePermissions.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.permadmin;
+
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.util.Enumeration;
+import java.util.NoSuchElementException;
+import org.osgi.framework.Bundle;
+
+public final class BundlePermissions extends PermissionCollection {
+ private static final long serialVersionUID = -5443618108312606612L;
+
+ // Note that this forces the Enumeration inner class to be loaded as soon as possible (see bug 119069)
+ static final Enumeration<Permission> EMPTY_ENUMERATION = new Enumeration<Permission>() {
+ public boolean hasMoreElements() {
+ return false;
+ }
+
+ public Permission nextElement() {
+ throw new NoSuchElementException();
+ }
+ };
+
+ private final Bundle bundle;
+ private final SecurityAdmin securityAdmin;
+ private final PermissionInfoCollection impliedPermissions;
+ private final PermissionInfoCollection restrictedPermissions;
+
+ public BundlePermissions(Bundle bundle, SecurityAdmin securityAdmin, PermissionInfoCollection impliedPermissions, PermissionInfoCollection restrictedPermissions) {
+ this.bundle = bundle;
+ this.securityAdmin = securityAdmin;
+ this.impliedPermissions = impliedPermissions;
+ this.restrictedPermissions = restrictedPermissions;
+ setReadOnly(); // collections are managed with ConditionalPermissionAdmin
+ }
+
+ public void add(Permission permission) {
+ throw new SecurityException();
+ }
+
+ public Enumeration<Permission> elements() {
+ // TODO return an empty enumeration for now;
+ // It does not seem possible to do this properly with multiple exports and conditional permissions.
+ // When looking to fix this be sure the Enumeration class is loaded as soon as possible (see bug 119069)
+ return EMPTY_ENUMERATION;
+ }
+
+ public boolean implies(Permission permission) {
+ // first check implied permissions
+ if ((impliedPermissions != null) && impliedPermissions.implies(permission))
+ return true;
+ // We must be allowed by the restricted permissions to have any hope of passing the check
+ if ((restrictedPermissions != null) && !restrictedPermissions.implies(permission))
+ return false;
+ return securityAdmin.checkPermission(permission, this);
+ }
+
+ public Bundle getBundle() {
+ return bundle;
+ }
+
+ void clearPermissionCache() {
+ if (impliedPermissions != null)
+ impliedPermissions.clearPermissionCache();
+ if (restrictedPermissions != null)
+ restrictedPermissions.clearPermissionCache();
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/EquinoxSecurityManager.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/EquinoxSecurityManager.java
new file mode 100644
index 000000000..10d5ce78e
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/EquinoxSecurityManager.java
@@ -0,0 +1,192 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.permadmin;
+
+import java.security.*;
+import java.util.*;
+import org.eclipse.osgi.internal.permadmin.SecurityRow.Decision;
+import org.osgi.service.condpermadmin.Condition;
+
+/**
+ *
+ * This security manager implements the ConditionalPermission processing for
+ * OSGi. It is to be used with ConditionalPermissionAdmin.
+ *
+ */
+public class EquinoxSecurityManager extends SecurityManager {
+ /*
+ * This is super goofy, but we need to make sure that the CheckContext and
+ * CheckPermissionAction classes load early. Otherwise, we run into problems later.
+ */
+ static {
+ Class<?> c;
+ c = CheckPermissionAction.class;
+ c = CheckContext.class;
+ c.getName(); // to prevent compiler warnings
+ }
+
+ static class CheckContext {
+ // A non zero depth indicates that we are doing a recursive permission check.
+ List<List<Decision[]>> depthCondSets = new ArrayList<List<Decision[]>>(2);
+ List<AccessControlContext> accs = new ArrayList<AccessControlContext>(2);
+ List<Class<?>> CondClassSet;
+
+ public int getDepth() {
+ return depthCondSets.size() - 1;
+ }
+ }
+
+ static class CheckPermissionAction implements PrivilegedAction<Object> {
+ Permission perm;
+ Object context;
+ EquinoxSecurityManager fsm;
+
+ CheckPermissionAction(EquinoxSecurityManager fsm, Permission perm, Object context) {
+ this.fsm = fsm;
+ this.perm = perm;
+ this.context = context;
+ }
+
+ public Object run() {
+ fsm.internalCheckPermission(perm, context);
+ return null;
+ }
+ }
+
+ private final ThreadLocal<CheckContext> localCheckContext = new ThreadLocal<CheckContext>();
+
+ boolean addConditionsForDomain(Decision[] results) {
+ CheckContext cc = localCheckContext.get();
+ if (cc == null) {
+ // We are being invoked in a weird way. Perhaps the ProtectionDomain is
+ // getting invoked directly.
+ return false;
+ }
+ List<Decision[]> condSets = cc.depthCondSets.get(cc.getDepth());
+ if (condSets == null) {
+ condSets = new ArrayList<Decision[]>(1);
+ cc.depthCondSets.set(cc.getDepth(), condSets);
+ }
+ condSets.add(results);
+ return true;
+ }
+
+ boolean inCheckPermission() {
+ return localCheckContext.get() != null;
+ }
+
+ public void checkPermission(Permission perm, Object context) {
+ AccessController.doPrivileged(new CheckPermissionAction(this, perm, context));
+ }
+
+ /**
+ * Gets the AccessControlContext currently being evaluated by
+ * the SecurityManager.
+ *
+ * @return the AccessControlContext currently being evaluated by the SecurityManager, or
+ * null if no AccessControlContext is being evaluated. Note: this method will
+ * return null if the permission check is being done directly on the AccessControlContext
+ * rather than the SecurityManager.
+ */
+ public AccessControlContext getContextToBeChecked() {
+ CheckContext cc = localCheckContext.get();
+ if (cc != null && cc.accs != null && !cc.accs.isEmpty())
+ return cc.accs.get(cc.accs.size() - 1);
+ return null;
+ }
+
+ void internalCheckPermission(Permission perm, Object context) {
+ AccessControlContext acc = (AccessControlContext) context;
+ CheckContext cc = localCheckContext.get();
+ if (cc == null) {
+ cc = new CheckContext();
+ localCheckContext.set(cc);
+ }
+ cc.depthCondSets.add(null); // initialize postponed condition set to null
+ cc.accs.add(acc);
+ try {
+ acc.checkPermission(perm);
+ // We want to pop the first set of postponed conditions and process them
+ List<Decision[]> conditionSets = cc.depthCondSets.get(cc.getDepth());
+ if (conditionSets == null)
+ return;
+ // TODO the spec seems impossible to implement just doing the simple thing for now
+ Map<Class<? extends Condition>, Dictionary<Object, Object>> conditionDictionaries = new HashMap<Class<? extends Condition>, Dictionary<Object, Object>>();
+ for (Decision[] domainDecisions : conditionSets) {
+ boolean grant = false;
+ for (int i = 0; i < domainDecisions.length; i++) {
+ if (domainDecisions[i] == null)
+ break;
+ if ((domainDecisions[i].decision & SecurityTable.ABSTAIN) != 0)
+ continue;
+ if ((domainDecisions[i].decision & SecurityTable.POSTPONED) == 0) {
+ // hit an immediate decision; use it
+ if ((domainDecisions[i].decision & SecurityTable.GRANTED) != 0)
+ grant = true;
+ break;
+ }
+ int decision = getPostponedDecision(domainDecisions[i], conditionDictionaries, cc);
+ if ((decision & SecurityTable.ABSTAIN) != 0)
+ continue;
+ if ((decision & SecurityTable.GRANTED) != 0)
+ grant = true;
+ break;
+ }
+ if (!grant)
+ // did not find a condition to grant the permission for this domain
+ throw new SecurityException("Conditions not satisfied"); //$NON-NLS-1$
+ // continue to next domain
+ }
+
+ } finally {
+ cc.depthCondSets.remove(cc.getDepth());
+ cc.accs.remove(cc.accs.size() - 1);
+ }
+ }
+
+ private int getPostponedDecision(Decision decision, Map<Class<? extends Condition>, Dictionary<Object, Object>> conditionDictionaries, CheckContext cc) {
+ Condition[] postponed = decision.postponed;
+ for (int i = 0; i < postponed.length; i++) {
+ Dictionary<Object, Object> condContext = conditionDictionaries.get(postponed[i].getClass());
+ if (condContext == null) {
+ condContext = new Hashtable<Object, Object>();
+ conditionDictionaries.put(postponed[i].getClass(), condContext);
+ }
+ // prevent recursion into Condition
+ if (cc.CondClassSet == null)
+ cc.CondClassSet = new ArrayList<Class<?>>(2);
+ if (cc.CondClassSet.contains(postponed[i].getClass()))
+ return SecurityTable.ABSTAIN;
+ cc.CondClassSet.add(postponed[i].getClass());
+ try {
+ // must call isMutable before calling isSatisfied according to the specification
+ boolean mutable = postponed[i].isMutable();
+ boolean isSatisfied = postponed[i].isSatisfied(new Condition[] {postponed[i]}, condContext);
+ decision.handleImmutable(postponed[i], isSatisfied, mutable);
+ if (!isSatisfied)
+ return SecurityTable.ABSTAIN;
+ } finally {
+ cc.CondClassSet.remove(postponed[i].getClass());
+ }
+ }
+ // call postponed conditions are satisfied return the decision
+ return decision.decision;
+ }
+
+ public void checkPermission(Permission perm) {
+ checkPermission(perm, getSecurityContext());
+ }
+
+ public Object getSecurityContext() {
+ return AccessController.getContext();
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/PermissionAdminTable.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/PermissionAdminTable.java
new file mode 100644
index 000000000..472c35009
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/PermissionAdminTable.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.permadmin;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.osgi.service.permissionadmin.PermissionInfo;
+
+public class PermissionAdminTable {
+ private final Map<String, PermissionInfoCollection> locations = new HashMap<String, PermissionInfoCollection>();
+
+ String[] getLocations() {
+ return locations.keySet().toArray(new String[locations.size()]);
+ }
+
+ PermissionInfo[] getPermissions(String location) {
+ PermissionInfoCollection collection = locations.get(location);
+ if (collection != null)
+ return collection.getPermissionInfos();
+ return null;
+ }
+
+ void setPermissions(String location, PermissionInfo[] permissions) {
+ if (permissions == null) {
+ locations.remove(location);
+ return;
+ }
+ locations.put(location, new PermissionInfoCollection(permissions));
+ }
+
+ PermissionInfoCollection getCollection(String location) {
+ return locations.get(location);
+ }
+
+ PermissionInfoCollection[] getCollections() {
+ String[] currentLocations = getLocations();
+ PermissionInfoCollection[] results = new PermissionInfoCollection[currentLocations.length];
+ for (int i = 0; i < results.length; i++)
+ results[i] = getCollection(currentLocations[i]);
+ return results;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/PermissionInfoCollection.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/PermissionInfoCollection.java
new file mode 100644
index 000000000..8d72ff1cd
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/PermissionInfoCollection.java
@@ -0,0 +1,132 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.permadmin;
+
+import java.io.File;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.security.*;
+import java.util.*;
+import org.osgi.service.permissionadmin.PermissionInfo;
+
+public final class PermissionInfoCollection extends PermissionCollection {
+ private static final long serialVersionUID = 3140511562980923957L;
+ /* Used to find permission constructors in addPermissions */
+ static private final Class<?> twoStringClassArray[] = new Class[] {String.class, String.class};
+ static private final Class<?> oneStringClassArray[] = new Class[] {String.class};
+ static private final Class<?> noArgClassArray[] = new Class[] {};
+ static private final Class<?>[][] permClassArrayArgs = new Class[][] {noArgClassArray, oneStringClassArray, twoStringClassArray};
+
+ /* @GuardedBy(cachedPermisssionCollections) */
+ private final Map<Class<? extends Permission>, PermissionCollection> cachedPermissionCollections = new HashMap<Class<? extends Permission>, PermissionCollection>();
+ private final boolean hasAllPermission;
+ private final PermissionInfo[] permInfos;
+
+ public PermissionInfoCollection(PermissionInfo[] permInfos) {
+ this.permInfos = permInfos;
+ boolean tempAllPermissions = false;
+ for (int i = 0; i < permInfos.length && !tempAllPermissions; i++)
+ if (permInfos[i].getType().equals(AllPermission.class.getName()))
+ tempAllPermissions = true;
+ this.hasAllPermission = tempAllPermissions;
+ setReadOnly(); // collections are managed with ConditionalPermissionAdmin
+ }
+
+ public void add(Permission arg0) {
+ throw new SecurityException();
+ }
+
+ public Enumeration<Permission> elements() {
+ // TODO return an empty enumeration for now;
+ return BundlePermissions.EMPTY_ENUMERATION;
+ }
+
+ public boolean implies(Permission perm) {
+ if (hasAllPermission)
+ return true;
+ Class<? extends Permission> permClass = perm.getClass();
+ PermissionCollection collection;
+ synchronized (cachedPermissionCollections) {
+ collection = cachedPermissionCollections.get(permClass);
+ }
+ // must populate the collection outside of the lock to prevent class loader deadlock
+ if (collection == null) {
+ collection = perm.newPermissionCollection();
+ if (collection == null)
+ collection = new PermissionsHash();
+ try {
+ addPermissions(collection, permClass);
+ } catch (Exception e) {
+ throw (SecurityException) new SecurityException("Exception creating permissions: " + permClass + ": " + e.getMessage()).initCause(e); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ synchronized (cachedPermissionCollections) {
+ // check to see if another thread beat this thread at adding the collection
+ PermissionCollection exists = cachedPermissionCollections.get(permClass);
+ if (exists != null)
+ collection = exists;
+ else
+ cachedPermissionCollections.put(permClass, collection);
+ }
+ }
+ return collection.implies(perm);
+ }
+
+ PermissionInfo[] getPermissionInfos() {
+ return permInfos;
+ }
+
+ private void addPermissions(PermissionCollection collection, Class<? extends Permission> permClass) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
+ String permClassName = permClass.getName();
+ Constructor<? extends Permission> constructor = null;
+ int numArgs = -1;
+ for (int i = permClassArrayArgs.length - 1; i >= 0; i--) {
+ try {
+ constructor = permClass.getConstructor(permClassArrayArgs[i]);
+ numArgs = i;
+ break;
+ } catch (NoSuchMethodException e) {
+ // ignore
+ }
+ }
+ if (constructor == null)
+ throw new NoSuchMethodException(permClass.getName() + ".<init>()"); //$NON-NLS-1$
+ /*
+ * TODO: We need to cache the permission constructors to enhance performance (see bug 118813).
+ */
+ for (int i = 0; i < permInfos.length; i++) {
+ if (permInfos[i].getType().equals(permClassName)) {
+ String args[] = new String[numArgs];
+ if (numArgs > 0)
+ args[0] = permInfos[i].getName();
+ if (numArgs > 1)
+ args[1] = permInfos[i].getActions();
+
+ if (permInfos[i].getType().equals("java.io.FilePermission")) { //$NON-NLS-1$
+ // map FilePermissions for relative names to the bundle's data area
+ if (!args[0].equals("<<ALL FILES>>")) { //$NON-NLS-1$
+ File file = new File(args[0]);
+ if (!file.isAbsolute()) { // relative name
+ // TODO need to figure out how to do relative FilePermissions from the dataFile
+ continue;
+ }
+ }
+ }
+ collection.add(constructor.newInstance((Object[]) args));
+ }
+ }
+ }
+
+ void clearPermissionCache() {
+ synchronized (cachedPermissionCollections) {
+ cachedPermissionCollections.clear();
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/PermissionsHash.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/PermissionsHash.java
new file mode 100644
index 000000000..667f23e01
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/PermissionsHash.java
@@ -0,0 +1,103 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.permadmin;
+
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+/**
+ * A simple Hashtable based collection of Permission objects.
+ * <p>
+ * The class' .implies method simply scans each permission
+ * individually and asks if the permission should be granted.
+ * No addition semantics is provided by the collection, so it is
+ * not possible to grant permissions whose "grantedness" is
+ * split across multiple stored Permissions.
+ * <p>
+ * Instances of this class can be used to store heterogeneous
+ * collections of permissions, as long as it is not necessary
+ * to remember when multiple occurances of .equal permissions
+ * are added.
+ *
+ */
+class PermissionsHash extends PermissionCollection {
+ private static final long serialVersionUID = 3258408426341284153L;
+ /**
+ * A hashtable to store the elements of the collection.
+ */
+ Hashtable<Permission, Permission> perms = new Hashtable<Permission, Permission>(8);
+
+ /**
+ * Constructs a new instance of this class.
+ *
+ */
+ public PermissionsHash() {
+ super();
+ }
+
+ /**
+ * Adds the argument to the collection.
+ *
+ * @param perm java.security.Permission
+ * the permission to add to the collection.
+ * @exception IllegalStateException
+ * if the collection is read only.
+ */
+ public void add(Permission perm) {
+ if (isReadOnly()) {
+ throw new SecurityException();
+ }
+
+ perms.put(perm, perm);
+ }
+
+ /**
+ * Answers an enumeration of the permissions
+ * in the receiver.
+ *
+ * @return Enumeration
+ * the permissions in the receiver.
+ */
+ public Enumeration<Permission> elements() {
+ return perms.keys();
+ }
+
+ /**
+ * Indicates whether the argument permission is implied
+ * by the permissions contained in the receiver.
+ *
+ * @return boolean
+ * <code>true</code> if the argument permission
+ * is implied by the permissions in the receiver,
+ * and <code>false</code> if it is not.
+ * @param perm java.security.Permission
+ * the permission to check
+ */
+ public boolean implies(Permission perm) {
+ Permission p = perms.get(perm);
+
+ if ((p != null) && p.implies(perm)) {
+ return true;
+ }
+
+ Enumeration<Permission> permsEnum = elements();
+
+ while (permsEnum.hasMoreElements()) {
+ if (permsEnum.nextElement().implies(perm)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/SecurePermissionStorage.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/SecurePermissionStorage.java
new file mode 100644
index 000000000..270212b85
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/SecurePermissionStorage.java
@@ -0,0 +1,109 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.permadmin;
+
+import java.io.IOException;
+import java.security.*;
+import org.eclipse.osgi.framework.adaptor.PermissionStorage;
+
+/**
+ * PermissionStorage privileged action class. This class is not thread safe. Callers
+ * must ensure multiple threads do not call methods on this class at the same time.
+ */
+public class SecurePermissionStorage implements PermissionStorage, PrivilegedExceptionAction<String[]> {
+ private final PermissionStorage storage;
+ private String location;
+ private String[] data;
+ private String[] infos;
+ private int action;
+ private static final int GET = 1;
+ private static final int SET = 2;
+ private static final int LOCATION = 3;
+ private static final int GET_INFOS = 4;
+ private static final int SAVE_INFOS = 5;
+
+ public SecurePermissionStorage(PermissionStorage storage) {
+ this.storage = storage;
+ }
+
+ public String[] run() throws IOException {
+ switch (action) {
+ case GET :
+ return storage.getPermissionData(location);
+ case SET :
+ storage.setPermissionData(location, data);
+ return null;
+ case LOCATION :
+ return storage.getLocations();
+ case SAVE_INFOS :
+ storage.saveConditionalPermissionInfos(infos);
+ return null;
+ case GET_INFOS :
+ return storage.getConditionalPermissionInfos();
+ }
+
+ throw new UnsupportedOperationException();
+ }
+
+ public String[] getPermissionData(String loc) throws IOException {
+ this.location = loc;
+ this.action = GET;
+
+ try {
+ return AccessController.doPrivileged(this);
+ } catch (PrivilegedActionException e) {
+ throw (IOException) e.getException();
+ }
+ }
+
+ public String[] getLocations() throws IOException {
+ this.action = LOCATION;
+
+ try {
+ return AccessController.doPrivileged(this);
+ } catch (PrivilegedActionException e) {
+ throw (IOException) e.getException();
+ }
+ }
+
+ public void setPermissionData(String location, String[] data) throws IOException {
+ this.location = location;
+ this.data = data;
+ this.action = SET;
+
+ try {
+ AccessController.doPrivileged(this);
+ } catch (PrivilegedActionException e) {
+ throw (IOException) e.getException();
+ }
+ }
+
+ public void saveConditionalPermissionInfos(String[] updatedInfos) throws IOException {
+ this.action = SAVE_INFOS;
+ this.infos = updatedInfos;
+ try {
+ AccessController.doPrivileged(this);
+ } catch (PrivilegedActionException e) {
+ throw (IOException) e.getException();
+ }
+
+ }
+
+ public String[] getConditionalPermissionInfos() throws IOException {
+ this.action = GET_INFOS;
+ try {
+ return AccessController.doPrivileged(this);
+ } catch (PrivilegedActionException e) {
+ throw (IOException) e.getException();
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/SecurityAdmin.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/SecurityAdmin.java
new file mode 100644
index 000000000..d59644242
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/SecurityAdmin.java
@@ -0,0 +1,874 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.permadmin;
+
+import java.io.*;
+import java.math.BigInteger;
+import java.net.URL;
+import java.security.*;
+import java.security.cert.*;
+import java.util.*;
+import org.eclipse.osgi.framework.adaptor.BundleProtectionDomain;
+import org.eclipse.osgi.framework.adaptor.PermissionStorage;
+import org.eclipse.osgi.framework.internal.core.*;
+import org.eclipse.osgi.framework.internal.core.Constants;
+import org.osgi.framework.*;
+import org.osgi.service.condpermadmin.*;
+import org.osgi.service.permissionadmin.PermissionAdmin;
+import org.osgi.service.permissionadmin.PermissionInfo;
+
+public final class SecurityAdmin implements PermissionAdmin, ConditionalPermissionAdmin {
+ private static final PermissionCollection DEFAULT_DEFAULT;
+ static {
+ AllPermission allPerm = new AllPermission();
+ DEFAULT_DEFAULT = allPerm.newPermissionCollection();
+ if (DEFAULT_DEFAULT != null)
+ DEFAULT_DEFAULT.add(allPerm);
+ }
+
+ private static final String ADMIN_IMPLIED_ACTIONS = AdminPermission.RESOURCE + ',' + AdminPermission.METADATA + ',' + AdminPermission.CLASS + ',' + AdminPermission.CONTEXT;
+ private static final PermissionInfo[] EMPTY_PERM_INFO = new PermissionInfo[0];
+ /* @GuardedBy(lock) */
+ private final PermissionAdminTable permAdminTable = new PermissionAdminTable();
+ /* @GuardedBy(lock) */
+ private SecurityTable condAdminTable;
+ /* @GuardedBy(lock) */
+ private PermissionInfoCollection permAdminDefaults;
+ /* @GuardedBy(lock) */
+ private long timeStamp = 0;
+ /* @GuardedBy(lock) */
+ private long nextID = System.currentTimeMillis();
+ /* @GuardedBy(lock) */
+ private final PermissionStorage permissionStorage;
+ private final Object lock = new Object();
+ private final Framework framework;
+ private final PermissionInfo[] impliedPermissionInfos;
+ private final EquinoxSecurityManager supportedSecurityManager;
+
+ private SecurityAdmin(EquinoxSecurityManager supportedSecurityManager, Framework framework, PermissionInfo[] impliedPermissionInfos, PermissionInfoCollection permAdminDefaults) {
+ this.supportedSecurityManager = supportedSecurityManager;
+ this.framework = framework;
+ this.impliedPermissionInfos = impliedPermissionInfos;
+ this.permAdminDefaults = permAdminDefaults;
+ this.permissionStorage = null;
+ }
+
+ public SecurityAdmin(EquinoxSecurityManager supportedSecurityManager, Framework framework, PermissionStorage permissionStorage) throws IOException {
+ this.supportedSecurityManager = supportedSecurityManager;
+ this.framework = framework;
+ this.permissionStorage = new SecurePermissionStorage(permissionStorage);
+ this.impliedPermissionInfos = SecurityAdmin.getPermissionInfos(getClass().getResource(Constants.OSGI_BASE_IMPLIED_PERMISSIONS), framework);
+ String[] encodedDefaultInfos = permissionStorage.getPermissionData(null);
+ PermissionInfo[] defaultInfos = getPermissionInfos(encodedDefaultInfos);
+ if (defaultInfos != null)
+ permAdminDefaults = new PermissionInfoCollection(defaultInfos);
+ String[] locations = permissionStorage.getLocations();
+ if (locations != null) {
+ for (int i = 0; i < locations.length; i++) {
+ String[] encodedLocationInfos = permissionStorage.getPermissionData(locations[i]);
+ if (encodedLocationInfos != null) {
+ PermissionInfo[] locationInfos = getPermissionInfos(encodedLocationInfos);
+ permAdminTable.setPermissions(locations[i], locationInfos);
+ }
+ }
+ }
+ String[] encodedCondPermInfos = permissionStorage.getConditionalPermissionInfos();
+ if (encodedCondPermInfos == null)
+ condAdminTable = new SecurityTable(this, new SecurityRow[0]);
+ else {
+ SecurityRow[] rows = new SecurityRow[encodedCondPermInfos.length];
+ try {
+ for (int i = 0; i < rows.length; i++)
+ rows[i] = SecurityRow.createSecurityRow(this, encodedCondPermInfos[i]);
+ } catch (IllegalArgumentException e) {
+ // TODO should log
+ // bad format persisted in storage; start clean
+ rows = new SecurityRow[0];
+ }
+ condAdminTable = new SecurityTable(this, rows);
+ }
+ }
+
+ private static PermissionInfo[] getPermissionInfos(String[] encodedInfos) {
+ if (encodedInfos == null)
+ return null;
+ PermissionInfo[] results = new PermissionInfo[encodedInfos.length];
+ for (int i = 0; i < results.length; i++)
+ results[i] = new PermissionInfo(encodedInfos[i]);
+ return results;
+ }
+
+ boolean checkPermission(Permission permission, BundlePermissions bundlePermissions) {
+ // check permissions by location
+ PermissionInfoCollection locationCollection;
+ SecurityTable curCondAdminTable;
+ PermissionInfoCollection curPermAdminDefaults;
+ // save off the current state of the world while holding the lock
+ synchronized (lock) {
+ // get location the hard way to avoid permission check
+ Bundle bundle = bundlePermissions.getBundle();
+ locationCollection = bundle instanceof AbstractBundle ? permAdminTable.getCollection(((AbstractBundle) bundle).getBundleData().getLocation()) : null;
+ curCondAdminTable = condAdminTable;
+ curPermAdminDefaults = permAdminDefaults;
+ }
+ if (locationCollection != null)
+ return locationCollection.implies(permission);
+ // if conditional admin table is empty the fall back to defaults
+ if (curCondAdminTable.isEmpty())
+ return curPermAdminDefaults != null ? curPermAdminDefaults.implies(permission) : DEFAULT_DEFAULT.implies(permission);
+ // check the condition table
+ int result = curCondAdminTable.evaluate(bundlePermissions, permission);
+ if ((result & SecurityTable.GRANTED) != 0)
+ return true;
+ if ((result & SecurityTable.DENIED) != 0)
+ return false;
+ if ((result & SecurityTable.POSTPONED) != 0)
+ return true;
+ return false;
+ }
+
+ public PermissionInfo[] getDefaultPermissions() {
+ synchronized (lock) {
+ if (permAdminDefaults == null)
+ return null;
+ return permAdminDefaults.getPermissionInfos();
+ }
+ }
+
+ public String[] getLocations() {
+ synchronized (lock) {
+ String[] results = permAdminTable.getLocations();
+ return results.length == 0 ? null : results;
+ }
+ }
+
+ public PermissionInfo[] getPermissions(String location) {
+ synchronized (lock) {
+ return permAdminTable.getPermissions(location);
+ }
+ }
+
+ public void setDefaultPermissions(PermissionInfo[] permissions) {
+ checkAllPermission();
+ synchronized (lock) {
+ if (permissions == null)
+ permAdminDefaults = null;
+ else
+ permAdminDefaults = new PermissionInfoCollection(permissions);
+ try {
+ permissionStorage.setPermissionData(null, getEncodedPermissionInfos(permissions));
+ } catch (IOException e) {
+ // log
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private static void checkAllPermission() {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null)
+ sm.checkPermission(new AllPermission());
+ }
+
+ private static String[] getEncodedPermissionInfos(PermissionInfo[] permissions) {
+ if (permissions == null)
+ return null;
+ String[] encoded = new String[permissions.length];
+ for (int i = 0; i < encoded.length; i++)
+ encoded[i] = permissions[i].getEncoded();
+ return encoded;
+ }
+
+ public void setPermissions(String location, PermissionInfo[] permissions) {
+ checkAllPermission();
+ synchronized (lock) {
+ permAdminTable.setPermissions(location, permissions);
+ try {
+ permissionStorage.setPermissionData(location, getEncodedPermissionInfos(permissions));
+ } catch (IOException e) {
+ // TODO log
+ e.printStackTrace();
+ }
+ }
+ }
+
+ void delete(SecurityRow securityRow, boolean firstTry) {
+ ConditionalPermissionUpdate update = newConditionalPermissionUpdate();
+ @SuppressWarnings("unchecked")
+ List<ConditionalPermissionInfo> rows = update.getConditionalPermissionInfos();
+ for (Iterator<ConditionalPermissionInfo> iRows = rows.iterator(); iRows.hasNext();) {
+ ConditionalPermissionInfo info = iRows.next();
+ if (securityRow.getName().equals(info.getName())) {
+ iRows.remove();
+ synchronized (lock) {
+ if (!update.commit()) {
+ if (firstTry)
+ // try again
+ delete(securityRow, false);
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ /**
+ * @deprecated
+ */
+ public ConditionalPermissionInfo addConditionalPermissionInfo(ConditionInfo[] conds, PermissionInfo[] perms) {
+ return setConditionalPermissionInfo(null, conds, perms, true);
+ }
+
+ public ConditionalPermissionInfo newConditionalPermissionInfo(String name, ConditionInfo[] conditions, PermissionInfo[] permissions, String decision) {
+ return new SecurityRowSnapShot(name, conditions, permissions, decision);
+ }
+
+ public ConditionalPermissionInfo newConditionalPermissionInfo(String encoded) {
+ return SecurityRow.createSecurityRowSnapShot(encoded);
+ }
+
+ public ConditionalPermissionUpdate newConditionalPermissionUpdate() {
+ synchronized (lock) {
+ return new SecurityTableUpdate(this, condAdminTable.getRows(), timeStamp);
+ }
+ }
+
+ public AccessControlContext getAccessControlContext(String[] signers) {
+ SecurityAdmin snapShot = getSnapShot();
+ return new AccessControlContext(new ProtectionDomain[] {createProtectionDomain(createMockBundle(signers), snapShot)});
+ }
+
+ /**
+ * @deprecated
+ */
+ public ConditionalPermissionInfo getConditionalPermissionInfo(String name) {
+ synchronized (lock) {
+ return condAdminTable.getRow(name);
+ }
+ }
+
+ /**
+ * @deprecated
+ */
+ public Enumeration<ConditionalPermissionInfo> getConditionalPermissionInfos() {
+ // could implement our own Enumeration, but we don't care about performance here. Just do something simple:
+ synchronized (lock) {
+ SecurityRow[] rows = condAdminTable.getRows();
+ List<ConditionalPermissionInfo> vRows = new ArrayList<ConditionalPermissionInfo>(rows.length);
+ for (int i = 0; i < rows.length; i++)
+ vRows.add(rows[i]);
+ return Collections.enumeration(vRows);
+ }
+ }
+
+ /**
+ * @deprecated
+ */
+ public ConditionalPermissionInfo setConditionalPermissionInfo(String name, ConditionInfo[] conds, PermissionInfo[] perms) {
+ return setConditionalPermissionInfo(name, conds, perms, true);
+ }
+
+ private SecurityAdmin getSnapShot() {
+ SecurityAdmin sa;
+ synchronized (lock) {
+ sa = new SecurityAdmin(supportedSecurityManager, framework, impliedPermissionInfos, permAdminDefaults);
+ SecurityRow[] rows = condAdminTable.getRows();
+ SecurityRow[] rowsSnapShot = new SecurityRow[rows.length];
+ for (int i = 0; i < rows.length; i++)
+ rowsSnapShot[i] = new SecurityRow(sa, rows[i].getName(), rows[i].getConditionInfos(), rows[i].getPermissionInfos(), rows[i].getAccessDecision());
+ sa.condAdminTable = new SecurityTable(sa, rowsSnapShot);
+ }
+ return sa;
+ }
+
+ private ConditionalPermissionInfo setConditionalPermissionInfo(String name, ConditionInfo[] conds, PermissionInfo[] perms, boolean firstTry) {
+ ConditionalPermissionUpdate update = newConditionalPermissionUpdate();
+ @SuppressWarnings("unchecked")
+ List<ConditionalPermissionInfo> rows = update.getConditionalPermissionInfos();
+ ConditionalPermissionInfo newInfo = newConditionalPermissionInfo(name, conds, perms, ConditionalPermissionInfo.ALLOW);
+ int index = -1;
+ if (name != null) {
+ for (int i = 0; i < rows.size() && index < 0; i++) {
+ ConditionalPermissionInfo info = rows.get(i);
+ if (name.equals(info.getName())) {
+ index = i;
+ }
+ }
+ }
+ if (index < 0) {
+ // must always add to the beginning (bug 303930)
+ rows.add(0, newInfo);
+ index = 0;
+ } else {
+ rows.set(index, newInfo);
+ }
+ synchronized (lock) {
+ if (!update.commit()) {
+ if (firstTry)
+ // try again
+ setConditionalPermissionInfo(name, conds, perms, false);
+ }
+ return condAdminTable.getRow(index);
+ }
+ }
+
+ boolean commit(List<ConditionalPermissionInfo> rows, long updateStamp) {
+ checkAllPermission();
+ synchronized (lock) {
+ if (updateStamp != timeStamp)
+ return false;
+ SecurityRow[] newRows = new SecurityRow[rows.size()];
+ Collection<String> names = new ArrayList<String>();
+ for (int i = 0; i < newRows.length; i++) {
+ Object rowObj = rows.get(i);
+ if (!(rowObj instanceof ConditionalPermissionInfo))
+ throw new IllegalStateException("Invalid type \"" + rowObj.getClass().getName() + "\" at row: " + i); //$NON-NLS-1$//$NON-NLS-2$
+ ConditionalPermissionInfo infoBaseRow = (ConditionalPermissionInfo) rowObj;
+ String name = infoBaseRow.getName();
+ if (name == null)
+ name = generateName();
+ if (names.contains(name))
+ throw new IllegalStateException("Duplicate name \"" + name + "\" at row: " + i); //$NON-NLS-1$//$NON-NLS-2$
+ names.add(name);
+ newRows[i] = new SecurityRow(this, name, infoBaseRow.getConditionInfos(), infoBaseRow.getPermissionInfos(), infoBaseRow.getAccessDecision());
+ }
+ condAdminTable = new SecurityTable(this, newRows);
+ try {
+ permissionStorage.saveConditionalPermissionInfos(condAdminTable.getEncodedRows());
+ } catch (IOException e) {
+ // TODO log
+ e.printStackTrace();
+ }
+ timeStamp += 1;
+ return true;
+ }
+ }
+
+ /* GuardedBy(lock) */
+ private String generateName() {
+ return "generated_" + Long.toString(nextID++); //$NON-NLS-1$;
+ }
+
+ public BundleProtectionDomain createProtectionDomain(Bundle bundle) {
+ return createProtectionDomain(bundle, this);
+ }
+
+ private BundleProtectionDomain createProtectionDomain(Bundle bundle, SecurityAdmin sa) {
+ PermissionInfoCollection impliedPermissions = getImpliedPermission(bundle);
+ PermissionInfo[] restrictedInfos = getFileRelativeInfos(SecurityAdmin.getPermissionInfos(bundle.getEntry("OSGI-INF/permissions.perm"), framework), bundle); //$NON-NLS-1$
+ PermissionInfoCollection restrictedPermissions = restrictedInfos == null ? null : new PermissionInfoCollection(restrictedInfos);
+ BundlePermissions bundlePermissions = new BundlePermissions(bundle, sa, impliedPermissions, restrictedPermissions);
+ return new BundleProtectionDomain(bundlePermissions, null, bundle);
+ }
+
+ private PermissionInfoCollection getImpliedPermission(Bundle bundle) {
+ if (impliedPermissionInfos == null)
+ return null;
+ // create the implied AdminPermission actions for this bundle
+ PermissionInfo impliedAdminPermission = new PermissionInfo(AdminPermission.class.getName(), "(id=" + bundle.getBundleId() + ")", ADMIN_IMPLIED_ACTIONS); //$NON-NLS-1$ //$NON-NLS-2$
+ PermissionInfo[] bundleImpliedInfos = new PermissionInfo[impliedPermissionInfos.length + 1];
+ System.arraycopy(impliedPermissionInfos, 0, bundleImpliedInfos, 0, impliedPermissionInfos.length);
+ bundleImpliedInfos[impliedPermissionInfos.length] = impliedAdminPermission;
+ return new PermissionInfoCollection(getFileRelativeInfos(bundleImpliedInfos, bundle));
+ }
+
+ private PermissionInfo[] getFileRelativeInfos(PermissionInfo[] permissionInfos, Bundle bundle) {
+ if (permissionInfos == null || !(bundle instanceof AbstractBundle))
+ return permissionInfos;
+ PermissionInfo[] results = new PermissionInfo[permissionInfos.length];
+ for (int i = 0; i < permissionInfos.length; i++) {
+ results[i] = permissionInfos[i];
+ if ("java.io.FilePermission".equals(permissionInfos[i].getType())) { //$NON-NLS-1$
+ if (!"<<ALL FILES>>".equals(permissionInfos[i].getName())) { //$NON-NLS-1$
+ File file = new File(permissionInfos[i].getName());
+ if (!file.isAbsolute()) { // relative name
+ File target = ((AbstractBundle) bundle).getBundleData().getDataFile(permissionInfos[i].getName());
+ if (target != null)
+ results[i] = new PermissionInfo(permissionInfos[i].getType(), target.getPath(), permissionInfos[i].getActions());
+ }
+ }
+ }
+ }
+ return results;
+ }
+
+ public void clearCaches() {
+ PermissionInfoCollection[] permAdminCollections;
+ SecurityRow[] condAdminRows;
+ synchronized (lock) {
+ permAdminCollections = permAdminTable.getCollections();
+ condAdminRows = condAdminTable.getRows();
+ }
+ for (int i = 0; i < permAdminCollections.length; i++)
+ permAdminCollections[i].clearPermissionCache();
+ for (int i = 0; i < condAdminRows.length; i++)
+ condAdminRows[i].clearCaches();
+ }
+
+ EquinoxSecurityManager getSupportedSecurityManager() {
+ return supportedSecurityManager != null ? supportedSecurityManager : getSupportedSystemSecurityManager();
+ }
+
+ static private EquinoxSecurityManager getSupportedSystemSecurityManager() {
+ try {
+ EquinoxSecurityManager equinoxManager = (EquinoxSecurityManager) System.getSecurityManager();
+ return equinoxManager != null && equinoxManager.inCheckPermission() ? equinoxManager : null;
+ } catch (ClassCastException e) {
+ return null;
+ }
+ }
+
+ private static PermissionInfo[] getPermissionInfos(URL resource, Framework framework) {
+ if (resource == null)
+ return null;
+ PermissionInfo[] info = EMPTY_PERM_INFO;
+ DataInputStream in = null;
+ try {
+ in = new DataInputStream(resource.openStream());
+ List<PermissionInfo> permissions = new ArrayList<PermissionInfo>();
+ BufferedReader reader;
+ try {
+ reader = new BufferedReader(new InputStreamReader(in, "UTF8")); //$NON-NLS-1$
+ } catch (UnsupportedEncodingException e) {
+ reader = new BufferedReader(new InputStreamReader(in));
+ }
+
+ while (true) {
+ String line = reader.readLine();
+ if (line == null) /* EOF */
+ break;
+ line = line.trim();
+ if ((line.length() == 0) || line.startsWith("#") || line.startsWith("//")) /* comments *///$NON-NLS-1$ //$NON-NLS-2$
+ continue;
+
+ try {
+ permissions.add(new PermissionInfo(line));
+ } catch (IllegalArgumentException iae) {
+ /* incorrectly encoded permission */
+ if (framework != null)
+ framework.publishFrameworkEvent(FrameworkEvent.ERROR, framework.getBundle(0), iae);
+ }
+ }
+ int size = permissions.size();
+ if (size > 0)
+ info = permissions.toArray(new PermissionInfo[size]);
+ } catch (IOException e) {
+ // do nothing
+ } finally {
+ try {
+ if (in != null)
+ in.close();
+ } catch (IOException ee) {
+ // do nothing
+ }
+ }
+ return info;
+ }
+
+ private static Bundle createMockBundle(String[] signers) {
+ Map<X509Certificate, List<X509Certificate>> signersMap = new HashMap<X509Certificate, List<X509Certificate>>();
+ for (int i = 0; i < signers.length; i++) {
+ List<String> chain = parseDNchain(signers[i]);
+ List<X509Certificate> signersList = new ArrayList<X509Certificate>();
+ Principal subject = null, issuer = null;
+ X509Certificate first = null;
+ for (Iterator<String> iChain = chain.iterator(); iChain.hasNext();) {
+ subject = issuer == null ? new MockPrincipal(iChain.next()) : issuer;
+ issuer = iChain.hasNext() ? new MockPrincipal(iChain.next()) : subject;
+ X509Certificate cert = new MockX509Certificate(subject, issuer);
+ if (first == null)
+ first = cert;
+ signersList.add(cert);
+ }
+ if (subject != issuer)
+ signersList.add(new MockX509Certificate(issuer, issuer));
+ signersMap.put(first, signersList);
+ }
+ return new MockBundle(signersMap);
+ }
+
+ static class MockBundle implements Bundle {
+ private final Map<X509Certificate, List<X509Certificate>> signers;
+
+ MockBundle(Map<X509Certificate, List<X509Certificate>> signers) {
+ this.signers = signers;
+ }
+
+ public Enumeration<URL> findEntries(String path, String filePattern, boolean recurse) {
+ return null;
+ }
+
+ public BundleContext getBundleContext() {
+ return null;
+ }
+
+ public long getBundleId() {
+ return -1;
+ }
+
+ public URL getEntry(String path) {
+ return null;
+ }
+
+ public Enumeration<String> getEntryPaths(String path) {
+ return null;
+ }
+
+ public Dictionary<String, String> getHeaders() {
+ return new Hashtable<String, String>();
+ }
+
+ public Dictionary<String, String> getHeaders(String locale) {
+ return getHeaders();
+ }
+
+ public long getLastModified() {
+ return 0;
+ }
+
+ public String getLocation() {
+ return ""; //$NON-NLS-1$
+ }
+
+ public ServiceReference<?>[] getRegisteredServices() {
+ return null;
+ }
+
+ public URL getResource(String name) {
+ return null;
+ }
+
+ /**
+ * @throws IOException
+ */
+ public Enumeration<URL> getResources(String name) throws IOException {
+ return null;
+ }
+
+ public ServiceReference<?>[] getServicesInUse() {
+ return null;
+ }
+
+ public Map<X509Certificate, List<X509Certificate>> getSignerCertificates(int signersType) {
+ return new HashMap<X509Certificate, List<X509Certificate>>(signers);
+ }
+
+ public int getState() {
+ return Bundle.UNINSTALLED;
+ }
+
+ public String getSymbolicName() {
+ return null;
+ }
+
+ public Version getVersion() {
+ return Version.emptyVersion;
+ }
+
+ public boolean hasPermission(Object permission) {
+ return false;
+ }
+
+ /**
+ * @throws ClassNotFoundException
+ */
+ public Class<?> loadClass(String name) throws ClassNotFoundException {
+ throw new IllegalStateException();
+ }
+
+ /**
+ * @throws BundleException
+ */
+ public void start(int options) throws BundleException {
+ throw new IllegalStateException();
+ }
+
+ /**
+ * @throws BundleException
+ */
+ public void start() throws BundleException {
+ throw new IllegalStateException();
+ }
+
+ /**
+ * @throws BundleException
+ */
+ public void stop(int options) throws BundleException {
+ throw new IllegalStateException();
+ }
+
+ /**
+ * @throws BundleException
+ */
+ public void stop() throws BundleException {
+ throw new IllegalStateException();
+ }
+
+ /**
+ * @throws BundleException
+ */
+ public void uninstall() throws BundleException {
+ throw new IllegalStateException();
+ }
+
+ /**
+ * @throws BundleException
+ */
+ public void update() throws BundleException {
+ throw new IllegalStateException();
+ }
+
+ /**
+ * @throws BundleException
+ */
+ public void update(InputStream in) throws BundleException {
+ throw new IllegalStateException();
+ }
+
+ public int compareTo(Bundle o) {
+ return 0;
+ }
+
+ public <A> A adapt(Class<A> type) {
+ throw new IllegalStateException();
+ }
+
+ public File getDataFile(String filename) {
+ return null;
+ }
+ }
+
+ private static class MockX509Certificate extends X509Certificate {
+ private final Principal subject;
+ private final Principal issuer;
+
+ MockX509Certificate(Principal subject, Principal issuer) {
+ this.subject = subject;
+ this.issuer = issuer;
+ }
+
+ public Principal getSubjectDN() {
+ return subject;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj instanceof MockX509Certificate)
+ return subject.equals(((MockX509Certificate) obj).subject) && issuer.equals(((MockX509Certificate) obj).issuer);
+ return false;
+ }
+
+ public int hashCode() {
+ return subject.hashCode() + issuer.hashCode();
+ }
+
+ public String toString() {
+ return subject.toString();
+ }
+
+ /**
+ * @throws CertificateExpiredException
+ * @throws java.security.cert.CertificateNotYetValidException
+ */
+ public void checkValidity() throws CertificateExpiredException, java.security.cert.CertificateNotYetValidException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @throws java.security.cert.CertificateExpiredException
+ * @throws java.security.cert.CertificateNotYetValidException
+ */
+ public void checkValidity(Date var0) throws java.security.cert.CertificateExpiredException, java.security.cert.CertificateNotYetValidException {
+ throw new UnsupportedOperationException();
+ }
+
+ public int getBasicConstraints() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Principal getIssuerDN() {
+ return issuer;
+ }
+
+ public boolean[] getIssuerUniqueID() {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean[] getKeyUsage() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Date getNotAfter() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Date getNotBefore() {
+ throw new UnsupportedOperationException();
+ }
+
+ public BigInteger getSerialNumber() {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getSigAlgName() {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getSigAlgOID() {
+ throw new UnsupportedOperationException();
+ }
+
+ public byte[] getSigAlgParams() {
+ throw new UnsupportedOperationException();
+ }
+
+ public byte[] getSignature() {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean[] getSubjectUniqueID() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @throws CertificateEncodingException
+ */
+ public byte[] getTBSCertificate() throws CertificateEncodingException {
+ throw new UnsupportedOperationException();
+ }
+
+ public int getVersion() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @throws CertificateEncodingException
+ */
+ public byte[] getEncoded() throws CertificateEncodingException {
+ throw new UnsupportedOperationException();
+ }
+
+ public PublicKey getPublicKey() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @throws java.security.InvalidKeyException
+ * @throws java.security.NoSuchAlgorithmException
+ * @throws java.security.NoSuchProviderException
+ * @throws java.security.SignatureException
+ * @throws java.security.cert.CertificateException
+ */
+ public void verify(PublicKey var0) throws java.security.InvalidKeyException, java.security.NoSuchAlgorithmException, java.security.NoSuchProviderException, java.security.SignatureException, java.security.cert.CertificateException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @throws InvalidKeyException
+ * @throws NoSuchAlgorithmException
+ * @throws NoSuchProviderException
+ * @throws SignatureException
+ * @throws CertificateException
+ */
+ public void verify(PublicKey var0, String var1) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException, CertificateException {
+ throw new UnsupportedOperationException();
+ }
+
+ public Set<String> getCriticalExtensionOIDs() {
+ throw new UnsupportedOperationException();
+ }
+
+ public byte[] getExtensionValue(String var0) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Set<String> getNonCriticalExtensionOIDs() {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean hasUnsupportedCriticalExtension() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ private static class MockPrincipal implements Principal {
+ private final String name;
+
+ MockPrincipal(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof MockPrincipal) {
+ return name.equals(((MockPrincipal) obj).name);
+ }
+ return false;
+ }
+
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+ public String toString() {
+ return getName();
+ }
+ }
+
+ private static List<String> parseDNchain(String dnChain) {
+ if (dnChain == null) {
+ throw new IllegalArgumentException("The DN chain must not be null."); //$NON-NLS-1$
+ }
+ List<String> parsed = new ArrayList<String>();
+ int startIndex = 0;
+ startIndex = skipSpaces(dnChain, startIndex);
+ while (startIndex < dnChain.length()) {
+ int endIndex = startIndex;
+ boolean inQuote = false;
+ out: while (endIndex < dnChain.length()) {
+ char c = dnChain.charAt(endIndex);
+ switch (c) {
+ case '"' :
+ inQuote = !inQuote;
+ break;
+ case '\\' :
+ endIndex++; // skip the escaped char
+ break;
+ case ';' :
+ if (!inQuote)
+ break out;
+ }
+ endIndex++;
+ }
+ if (endIndex > dnChain.length()) {
+ throw new IllegalArgumentException("unterminated escape"); //$NON-NLS-1$
+ }
+ parsed.add(dnChain.substring(startIndex, endIndex));
+ startIndex = endIndex + 1;
+ startIndex = skipSpaces(dnChain, startIndex);
+ }
+ return parsed;
+ }
+
+ private static int skipSpaces(String dnChain, int startIndex) {
+ while (startIndex < dnChain.length() && dnChain.charAt(startIndex) == ' ') {
+ startIndex++;
+ }
+ return startIndex;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/SecurityRow.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/SecurityRow.java
new file mode 100644
index 000000000..25d269449
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/SecurityRow.java
@@ -0,0 +1,457 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.permadmin;
+
+import java.lang.reflect.*;
+import java.security.Permission;
+import java.util.*;
+import org.osgi.framework.Bundle;
+import org.osgi.service.condpermadmin.*;
+import org.osgi.service.permissionadmin.PermissionInfo;
+
+public final class SecurityRow implements ConditionalPermissionInfo {
+ /* Used to find condition constructors getConditions */
+ static final Class<?>[] conditionMethodArgs = new Class[] {Bundle.class, ConditionInfo.class};
+ static Condition[] ABSTAIN_LIST = new Condition[0];
+ static Condition[] SATISFIED_LIST = new Condition[0];
+ static final Decision DECISION_ABSTAIN = new Decision(SecurityTable.ABSTAIN, null, null, null);
+ static final Decision DECISION_GRANTED = new Decision(SecurityTable.GRANTED, null, null, null);
+ static final Decision DECISION_DENIED = new Decision(SecurityTable.DENIED, null, null, null);
+
+ private final SecurityAdmin securityAdmin;
+ private final String name;
+ private final ConditionInfo[] conditionInfos;
+ private final PermissionInfoCollection permissionInfoCollection;
+ private final boolean deny;
+ /* GuardedBy(bundleConditions) */
+ final Map<BundlePermissions, Condition[]> bundleConditions;
+
+ public SecurityRow(SecurityAdmin securityAdmin, String name, ConditionInfo[] conditionInfos, PermissionInfo[] permissionInfos, String decision) {
+ if (permissionInfos == null || permissionInfos.length == 0)
+ throw new IllegalArgumentException("It is invalid to have empty permissionInfos"); //$NON-NLS-1$
+ this.securityAdmin = securityAdmin;
+ this.conditionInfos = conditionInfos == null ? new ConditionInfo[0] : conditionInfos;
+ decision = decision.toLowerCase();
+ boolean d = ConditionalPermissionInfo.DENY.equals(decision);
+ boolean a = ConditionalPermissionInfo.ALLOW.equals(decision);
+ if (!(d | a))
+ throw new IllegalArgumentException("Invalid decision: " + decision); //$NON-NLS-1$
+ this.deny = d;
+ this.name = name;
+ this.permissionInfoCollection = new PermissionInfoCollection(permissionInfos);
+ if (conditionInfos == null || conditionInfos.length == 0)
+ bundleConditions = null;
+ else
+ bundleConditions = new HashMap<BundlePermissions, Condition[]>();
+ }
+
+ static SecurityRowSnapShot createSecurityRowSnapShot(String encoded) {
+ return (SecurityRowSnapShot) createConditionalPermissionInfo(null, encoded);
+ }
+
+ static SecurityRow createSecurityRow(SecurityAdmin securityAdmin, String encoded) {
+ return (SecurityRow) createConditionalPermissionInfo(securityAdmin, encoded);
+ }
+
+ private static ConditionalPermissionInfo createConditionalPermissionInfo(SecurityAdmin securityAdmin, String encoded) {
+ encoded = encoded.trim();
+ if (encoded.length() == 0)
+ throw new IllegalArgumentException("Empty encoded string is invalid"); //$NON-NLS-1$
+ char[] chars = encoded.toCharArray();
+ int end = encoded.length() - 1;
+ char lastChar = chars[end];
+ if (lastChar != '}' && lastChar != '"')
+ throw new IllegalArgumentException(encoded);
+ String encodedName = null;
+ if (lastChar == '"') {
+ // we have a name: an empty name must have at least 2 chars for the quotes
+ if (chars.length < 2)
+ throw new IllegalArgumentException(encoded);
+ int endName = encoded.length() - 1;
+ int startName = endName - 1;
+ while (startName > 0) {
+ if (chars[startName] == '"') {
+ startName--;
+ if (startName > 0 && chars[startName] == '\\')
+ startName--;
+ else {
+ startName++;
+ break;
+ }
+ }
+ startName--;
+ }
+ if (chars[startName] != '"')
+ throw new IllegalArgumentException(encoded);
+ encodedName = unescapeString(encoded.substring(startName + 1, endName));
+ end = encoded.lastIndexOf('}', startName);
+ }
+ int start = encoded.indexOf('{');
+ if (start < 0 || end < start)
+ throw new IllegalArgumentException(encoded);
+
+ String decision = encoded.substring(0, start);
+ decision = decision.trim();
+ if (decision.length() == 0 || (!ConditionalPermissionInfo.DENY.equalsIgnoreCase(decision) && !ConditionalPermissionInfo.ALLOW.equalsIgnoreCase(decision)))
+ throw new IllegalArgumentException(encoded);
+
+ List<ConditionInfo> condList = new ArrayList<ConditionInfo>();
+ List<PermissionInfo> permList = new ArrayList<PermissionInfo>();
+ int pos = start + 1;
+ while (pos < end) {
+ while (pos < end && chars[pos] != '[' && chars[pos] != '(')
+ pos++;
+ if (pos == end)
+ break; // no perms or conds left
+ int startPos = pos;
+ char endChar = chars[startPos] == '[' ? ']' : ')';
+ while (pos < end && chars[pos] != endChar) {
+ if (chars[pos] == '"') {
+ pos++;
+ while (chars[pos] != '"') {
+ if (chars[pos] == '\\')
+ pos++;
+ pos++;
+ }
+ }
+ pos++;
+ }
+ int endPos = pos;
+ String token = new String(chars, startPos, endPos - startPos + 1);
+ if (endChar == ']')
+ condList.add(new ConditionInfo(token));
+ else
+ permList.add(new PermissionInfo(token));
+ pos++;
+ }
+ if (permList.size() == 0)
+ throw new IllegalArgumentException("No Permission infos: " + encoded); //$NON-NLS-1$
+ ConditionInfo[] conds = condList.toArray(new ConditionInfo[condList.size()]);
+ PermissionInfo[] perms = permList.toArray(new PermissionInfo[permList.size()]);
+ if (securityAdmin == null)
+ return new SecurityRowSnapShot(encodedName, conds, perms, decision);
+ return new SecurityRow(securityAdmin, encodedName, conds, perms, decision);
+ }
+
+ static Object cloneArray(Object[] array) {
+ if (array == null)
+ return null;
+ Object result = Array.newInstance(array.getClass().getComponentType(), array.length);
+ System.arraycopy(array, 0, result, 0, array.length);
+ return result;
+ }
+
+ private static void escapeString(String str, StringBuffer output) {
+ int len = str.length();
+ for (int i = 0; i < len; i++) {
+ char c = str.charAt(i);
+ switch (c) {
+ case '"' :
+ case '\\' :
+ output.append('\\');
+ output.append(c);
+ break;
+ case '\r' :
+ output.append("\\r"); //$NON-NLS-1$
+ break;
+ case '\n' :
+ output.append("\\n"); //$NON-NLS-1$
+ break;
+ default :
+ output.append(c);
+ break;
+ }
+ }
+ }
+
+ private static String unescapeString(String str) {
+ StringBuffer output = new StringBuffer(str.length());
+ int end = str.length();
+ for (int i = 0; i < end; i++) {
+ char c = str.charAt(i);
+ if (c == '\\') {
+ i++;
+ if (i < end) {
+ c = str.charAt(i);
+ switch (c) {
+ case '"' :
+ case '\\' :
+ break;
+ case 'r' :
+ c = '\r';
+ break;
+ case 'n' :
+ c = '\n';
+ break;
+ default :
+ c = '\\';
+ i--;
+ break;
+ }
+ }
+ }
+ output.append(c);
+ }
+
+ return output.toString();
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public ConditionInfo[] getConditionInfos() {
+ // must make a copy for the public API method to prevent modification
+ return (ConditionInfo[]) cloneArray(conditionInfos);
+ }
+
+ ConditionInfo[] internalGetConditionInfos() {
+ return conditionInfos;
+ }
+
+ public String getAccessDecision() {
+ return deny ? ConditionalPermissionInfo.DENY : ConditionalPermissionInfo.ALLOW;
+ }
+
+ public PermissionInfo[] getPermissionInfos() {
+ // must make a copy for the public API method to prevent modification
+ return (PermissionInfo[]) cloneArray(permissionInfoCollection.getPermissionInfos());
+ }
+
+ PermissionInfo[] internalGetPermissionInfos() {
+ return permissionInfoCollection.getPermissionInfos();
+ }
+
+ /**
+ * @deprecated
+ */
+ public void delete() {
+ securityAdmin.delete(this, true);
+ }
+
+ Condition[] getConditions(Bundle bundle) {
+ Condition[] conditions = new Condition[conditionInfos.length];
+ for (int i = 0; i < conditionInfos.length; i++) {
+ /*
+ * TODO: Can we pre-get the Constructors in our own constructor
+ */
+ Class<?> clazz;
+ try {
+ clazz = Class.forName(conditionInfos[i].getType());
+ } catch (ClassNotFoundException e) {
+ /* If the class isn't there, we fail */
+ return null;
+ }
+ Constructor<?> constructor = null;
+ Method method = null;
+ try {
+ method = clazz.getMethod("getCondition", conditionMethodArgs); //$NON-NLS-1$
+ if ((method.getModifiers() & Modifier.STATIC) == 0)
+ method = null;
+ } catch (NoSuchMethodException e) {
+ // This is a normal case
+ }
+ if (method == null)
+ try {
+ constructor = clazz.getConstructor(conditionMethodArgs);
+ } catch (NoSuchMethodException e) {
+ // TODO should post a FrameworkEvent of type error here
+ conditions[i] = Condition.FALSE;
+ continue;
+ }
+
+ Object args[] = {bundle, conditionInfos[i]};
+ try {
+ if (method != null)
+ conditions[i] = (Condition) method.invoke(null, args);
+ else
+ conditions[i] = (Condition) constructor.newInstance(args);
+ } catch (Throwable t) {
+ // TODO should post a FrameworkEvent of type error here
+ conditions[i] = Condition.FALSE;
+ }
+ }
+ return conditions;
+ }
+
+ Decision evaluate(BundlePermissions bundlePermissions, Permission permission) {
+ if (bundleConditions == null || bundlePermissions == null)
+ return evaluatePermission(permission);
+ Condition[] conditions;
+ synchronized (bundleConditions) {
+ conditions = bundleConditions.get(bundlePermissions);
+ if (conditions == null) {
+ conditions = getConditions(bundlePermissions.getBundle());
+ bundleConditions.put(bundlePermissions, conditions);
+ }
+ }
+ if (conditions == ABSTAIN_LIST)
+ return DECISION_ABSTAIN;
+ if (conditions == SATISFIED_LIST)
+ return evaluatePermission(permission);
+
+ boolean empty = true;
+ List<Condition> postponedConditions = null;
+ Decision postponedPermCheck = null;
+ for (int i = 0; i < conditions.length; i++) {
+ Condition condition = conditions[i];
+ if (condition == null)
+ continue; // this condition must have been satisfied && !mutable in a previous check
+ if (!isPostponed(condition)) {
+ // must call isMutable before calling isSatisfied according to the specification.
+ boolean mutable = condition.isMutable();
+ if (condition.isSatisfied()) {
+ if (!mutable)
+ conditions[i] = null; // ignore this condition for future checks
+ } else {
+ if (!mutable)
+ // this will cause the row to always abstain; mark this to be ignored in future checks
+ synchronized (bundleConditions) {
+ bundleConditions.put(bundlePermissions, ABSTAIN_LIST);
+ }
+ return DECISION_ABSTAIN;
+ }
+ } else { // postponed case
+ if (postponedPermCheck == null)
+ // perform a permission check now
+ postponedPermCheck = evaluatePermission(permission);
+ if (postponedPermCheck == DECISION_ABSTAIN)
+ return postponedPermCheck; // no need to postpone the condition if the row abstains
+ // this row will deny or allow the permission; must queue the postponed condition
+ if (postponedConditions == null)
+ postponedConditions = new ArrayList<Condition>(1);
+ postponedConditions.add(condition);
+ }
+ empty &= conditions[i] == null;
+ }
+ if (empty) {
+ synchronized (bundleConditions) {
+ bundleConditions.put(bundlePermissions, SATISFIED_LIST);
+ }
+ }
+ if (postponedPermCheck != null)
+ return new Decision(postponedPermCheck.decision | SecurityTable.POSTPONED, postponedConditions.toArray(new Condition[postponedConditions.size()]), this, bundlePermissions);
+ return evaluatePermission(permission);
+ }
+
+ private boolean isPostponed(Condition condition) {
+ // postponed checks can only happen if we are using a supported security manager
+ return condition.isPostponed() && securityAdmin.getSupportedSecurityManager() != null;
+ }
+
+ private Decision evaluatePermission(Permission permission) {
+ return permissionInfoCollection.implies(permission) ? (deny ? DECISION_DENIED : DECISION_GRANTED) : DECISION_ABSTAIN;
+ }
+
+ public String toString() {
+ return getEncoded();
+ }
+
+ public String getEncoded() {
+ return getEncoded(name, conditionInfos, internalGetPermissionInfos(), deny);
+ }
+
+ public boolean equals(Object obj) {
+ // doing the simple (slow) thing for now
+ if (obj == this)
+ return true;
+ if (!(obj instanceof ConditionalPermissionInfo))
+ return false;
+ // we assume the encoded string provides a canonical (comparable) form
+ return getEncoded().equals(((ConditionalPermissionInfo) obj).getEncoded());
+ }
+
+ public int hashCode() {
+ return getHashCode(name, internalGetConditionInfos(), internalGetPermissionInfos(), getAccessDecision());
+ }
+
+ static int getHashCode(String name, ConditionInfo[] conds, PermissionInfo[] perms, String decision) {
+ int h = 31 * 17 + decision.hashCode();
+ for (int i = 0; i < conds.length; i++)
+ h = 31 * h + conds[i].hashCode();
+ for (int i = 0; i < perms.length; i++)
+ h = 31 * h + perms[i].hashCode();
+ if (name != null)
+ h = 31 * h + name.hashCode();
+ return h;
+ }
+
+ static String getEncoded(String name, ConditionInfo[] conditionInfos, PermissionInfo[] permissionInfos, boolean deny) {
+ StringBuffer result = new StringBuffer();
+ if (deny)
+ result.append(ConditionalPermissionInfo.DENY);
+ else
+ result.append(ConditionalPermissionInfo.ALLOW);
+ result.append(" { "); //$NON-NLS-1$
+ if (conditionInfos != null)
+ for (int i = 0; i < conditionInfos.length; i++)
+ result.append(conditionInfos[i].getEncoded()).append(' ');
+ if (permissionInfos != null)
+ for (int i = 0; i < permissionInfos.length; i++)
+ result.append(permissionInfos[i].getEncoded()).append(' ');
+ result.append('}');
+ if (name != null) {
+ result.append(" \""); //$NON-NLS-1$
+ escapeString(name, result);
+ result.append('"');
+ }
+ return result.toString();
+ }
+
+ PermissionInfoCollection getPermissionInfoCollection() {
+ return permissionInfoCollection;
+ }
+
+ void clearCaches() {
+ permissionInfoCollection.clearPermissionCache();
+ if (bundleConditions != null)
+ synchronized (bundleConditions) {
+ bundleConditions.clear();
+ }
+ }
+
+ static class Decision {
+ final int decision;
+ final Condition[] postponed;
+ private final SecurityRow row;
+ private final BundlePermissions bundlePermissions;
+
+ Decision(int decision, Condition[] postponed, SecurityRow row, BundlePermissions bundlePermissions) {
+ this.decision = decision;
+ this.postponed = postponed;
+ this.row = row;
+ this.bundlePermissions = bundlePermissions;
+ }
+
+ void handleImmutable(Condition condition, boolean isSatisfied, boolean mutable) {
+ if (mutable || !condition.isPostponed())
+ return; // do nothing
+ if (isSatisfied) {
+ synchronized (row.bundleConditions) {
+ Condition[] rowConditions = row.bundleConditions.get(bundlePermissions);
+ boolean isEmpty = true;
+ for (int i = 0; i < rowConditions.length; i++) {
+ if (rowConditions[i] == condition)
+ if (isSatisfied)
+ rowConditions[i] = null;
+ isEmpty &= rowConditions[i] == null;
+ }
+ if (isEmpty)
+ row.bundleConditions.put(bundlePermissions, SATISFIED_LIST);
+ }
+ } else {
+ synchronized (row.bundleConditions) {
+ row.bundleConditions.put(bundlePermissions, ABSTAIN_LIST);
+ }
+ }
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/SecurityRowSnapShot.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/SecurityRowSnapShot.java
new file mode 100644
index 000000000..79767716f
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/SecurityRowSnapShot.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2009 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.permadmin;
+
+import org.osgi.service.condpermadmin.ConditionInfo;
+import org.osgi.service.condpermadmin.ConditionalPermissionInfo;
+import org.osgi.service.permissionadmin.PermissionInfo;
+
+public class SecurityRowSnapShot implements ConditionalPermissionInfo {
+
+ private final String name;
+ private final ConditionInfo[] conditionInfos;
+ private final PermissionInfo[] permissionInfos;
+ private final String decision;
+
+ public SecurityRowSnapShot(String name, ConditionInfo[] conditionInfos, PermissionInfo[] permissionInfos, String decision) {
+ if (permissionInfos == null || permissionInfos.length == 0)
+ throw new IllegalArgumentException("It is invalid to have empty permissionInfos"); //$NON-NLS-1$
+ decision = decision.toLowerCase();
+ boolean d = ConditionalPermissionInfo.DENY.equals(decision);
+ boolean a = ConditionalPermissionInfo.ALLOW.equals(decision);
+ if (!(d | a))
+ throw new IllegalArgumentException("Invalid decision: " + decision); //$NON-NLS-1$
+ conditionInfos = conditionInfos == null ? new ConditionInfo[0] : conditionInfos;
+ this.name = name;
+ // must create copies of the passed in arrays to prevent changes
+ this.conditionInfos = (ConditionInfo[]) SecurityRow.cloneArray(conditionInfos);
+ this.permissionInfos = (PermissionInfo[]) SecurityRow.cloneArray(permissionInfos);
+ this.decision = decision;
+ }
+
+ public ConditionInfo[] getConditionInfos() {
+ return (ConditionInfo[]) SecurityRow.cloneArray(conditionInfos);
+ }
+
+ public String getAccessDecision() {
+ return decision;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public PermissionInfo[] getPermissionInfos() {
+ return (PermissionInfo[]) SecurityRow.cloneArray(permissionInfos);
+ }
+
+ /**
+ * @deprecated
+ */
+ public void delete() {
+ throw new UnsupportedOperationException();
+ }
+
+ public String toString() {
+ return getEncoded();
+ }
+
+ public String getEncoded() {
+ return SecurityRow.getEncoded(name, conditionInfos, permissionInfos, DENY.equalsIgnoreCase(decision));
+ }
+
+ public boolean equals(Object obj) {
+ // doing the simple (slow) thing for now
+ if (obj == this)
+ return true;
+ if (!(obj instanceof ConditionalPermissionInfo))
+ return false;
+ // we assume the encoded string provides a canonical (comparable) form
+ return getEncoded().equals(((ConditionalPermissionInfo) obj).getEncoded());
+ }
+
+ public int hashCode() {
+ return SecurityRow.getHashCode(name, conditionInfos, permissionInfos, getAccessDecision());
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/SecurityTable.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/SecurityTable.java
new file mode 100644
index 000000000..72d587d48
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/SecurityTable.java
@@ -0,0 +1,133 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.permadmin;
+
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.util.Enumeration;
+import org.eclipse.osgi.internal.permadmin.SecurityRow.Decision;
+
+public class SecurityTable extends PermissionCollection {
+ private static final long serialVersionUID = -1800193310096318060L;
+ static final int GRANTED = 0x0001;
+ static final int DENIED = 0x0002;
+ static final int ABSTAIN = 0x0004;
+ static final int POSTPONED = 0x0008;
+
+ private final SecurityRow[] rows;
+ private final SecurityAdmin securityAdmin;
+
+ public SecurityTable(SecurityAdmin securityAdmin, SecurityRow[] rows) {
+ if (rows == null)
+ throw new NullPointerException("rows cannot be null!!"); //$NON-NLS-1$
+ this.rows = rows;
+ this.securityAdmin = securityAdmin;
+ }
+
+ boolean isEmpty() {
+ return rows.length == 0;
+ }
+
+ int evaluate(BundlePermissions bundlePermissions, Permission permission) {
+ if (isEmpty())
+ return ABSTAIN;
+ boolean postponed = false;
+ Decision[] results = new Decision[rows.length];
+ int immediateDecisionIdx = -1;
+ // evaluate each row
+ for (int i = 0; i < rows.length; i++) {
+ try {
+ results[i] = rows[i].evaluate(bundlePermissions, permission);
+ } catch (Throwable t) {
+ // TODO log?
+ results[i] = SecurityRow.DECISION_ABSTAIN;
+ }
+ if ((results[i].decision & ABSTAIN) != 0)
+ continue; // ignore this row and continue to next row
+ if ((results[i].decision & POSTPONED) != 0) {
+ // row is postponed; we can no longer return quickly on a denied decision
+ postponed = true;
+ continue; // continue to next row
+ }
+ if (!postponed)
+ // no postpones encountered yet; we can return the decision quickly
+ return results[i].decision; // return GRANTED or DENIED
+ // got an immediate answer; but it is after a postponed condition.
+ // no need to process the rest of the rows
+ immediateDecisionIdx = i;
+ break;
+ }
+ if (postponed) {
+ int immediateDecision = immediateDecisionIdx < 0 ? DENIED : results[immediateDecisionIdx].decision;
+ // iterate over all postponed conditions;
+ // if they all provide the same decision as the immediate decision then return the immediate decision
+ boolean allSameDecision = true;
+ int i = immediateDecisionIdx < 0 ? results.length - 1 : immediateDecisionIdx - 1;
+ for (; i >= 0 && allSameDecision; i--) {
+ if (results[i] == null)
+ continue;
+ if ((results[i].decision & POSTPONED) != 0) {
+ if ((results[i].decision & immediateDecision) == 0)
+ allSameDecision = false;
+ else
+ results[i] = SecurityRow.DECISION_ABSTAIN; // we can clear postpones with the same decision as the immediate
+ }
+ }
+ if (allSameDecision)
+ return immediateDecision;
+
+ // we now are forced to postpone; we need to also remember the postponed decisions and
+ // the immediate decision if there is one.
+ EquinoxSecurityManager equinoxManager = securityAdmin.getSupportedSecurityManager();
+ if (equinoxManager == null)
+ // TODO this is really an error condition.
+ // This should never happen. We checked for a supported manager when the row was postponed
+ return ABSTAIN;
+ equinoxManager.addConditionsForDomain(results);
+ }
+ return postponed ? POSTPONED : ABSTAIN;
+ }
+
+ SecurityRow getRow(int i) {
+ return rows.length <= i || i < 0 ? null : rows[i];
+ }
+
+ SecurityRow getRow(String name) {
+ for (int i = 0; i < rows.length; i++) {
+ if (name.equals(rows[i].getName()))
+ return rows[i];
+ }
+ return null;
+ }
+
+ SecurityRow[] getRows() {
+ return rows;
+ }
+
+ String[] getEncodedRows() {
+ String[] encoded = new String[rows.length];
+ for (int i = 0; i < rows.length; i++)
+ encoded[i] = rows[i].getEncoded();
+ return encoded;
+ }
+
+ public void add(Permission permission) {
+ throw new SecurityException();
+ }
+
+ public Enumeration<Permission> elements() {
+ return BundlePermissions.EMPTY_ENUMERATION;
+ }
+
+ public boolean implies(Permission permission) {
+ return (evaluate(null, permission) & SecurityTable.GRANTED) != 0;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/SecurityTableUpdate.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/SecurityTableUpdate.java
new file mode 100644
index 000000000..286e328cc
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/SecurityTableUpdate.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.permadmin;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.osgi.service.condpermadmin.ConditionalPermissionInfo;
+import org.osgi.service.condpermadmin.ConditionalPermissionUpdate;
+
+public class SecurityTableUpdate implements ConditionalPermissionUpdate {
+
+ private final SecurityAdmin securityAdmin;
+ private final List<ConditionalPermissionInfo> rows;
+ private final long timeStamp;
+
+ public SecurityTableUpdate(SecurityAdmin securityAdmin, SecurityRow[] rows, long timeStamp) {
+ this.securityAdmin = securityAdmin;
+ this.timeStamp = timeStamp;
+ // must make a snap shot of the security rows.
+ this.rows = new ArrayList<ConditionalPermissionInfo>(rows.length);
+ for (int i = 0; i < rows.length; i++)
+ // Use SecurityRowSnapShot to prevent modification before commit
+ // and to throw exceptions from delete
+ this.rows.add(new SecurityRowSnapShot(rows[i].getName(), rows[i].internalGetConditionInfos(), rows[i].internalGetPermissionInfos(), rows[i].getAccessDecision()));
+ }
+
+ public boolean commit() {
+ return securityAdmin.commit(rows, timeStamp);
+ }
+
+ public List<ConditionalPermissionInfo> getConditionalPermissionInfos() {
+ // it is fine to return the internal list; it is a snap shot and we allow clients to modify it.
+ return rows;
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/default.permissions b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/default.permissions
new file mode 100644
index 000000000..e04a88990
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/default.permissions
@@ -0,0 +1,25 @@
+########################################################################
+# Copyright (c) 2003, 2005 IBM Corporation and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Contributors:
+# IBM Corporation - initial API and implementation
+########################################################################
+
+# Lines beginning with '#' or '//' are comments
+#
+# This file contains the default permissions to be granted
+# to bundles with no specific permission if there are no
+# default permission set. This file must be UTF8 encoded.
+#
+# In this file, FilePermissions with relative names are not
+# mapped to an individual bundle's data directory.
+#
+# The permissions are listed one per
+# line in PermissionInfo encoded format.
+# See org.osgi.service.permissionadmin.PermissionInfo
+
+(java.security.AllPermission)
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/implied.permissions b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/implied.permissions
new file mode 100644
index 000000000..1ddcfe994
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/permadmin/implied.permissions
@@ -0,0 +1,50 @@
+########################################################################
+# Copyright (c) 2003, 2010 IBM Corporation and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Contributors:
+# IBM Corporation - initial API and implementation
+########################################################################
+
+# Lines beginning with '#' or '//' are comments
+#
+# This file contains the implied permissions to be granted
+# to all bundles. This file must be UTF8 encoded.
+#
+# In this file, FilePermissions with relative names are
+# mapped to an individual bundle's data directory.
+#
+# The permissions are listed one per
+# line in PermissionInfo encoded format.
+# See org.osgi.service.permissionadmin.PermissionInfo
+
+(java.util.PropertyPermission "java.vendor" "read")
+(java.util.PropertyPermission "java.specification.version" "read")
+(java.util.PropertyPermission "line.separator" "read")
+(java.util.PropertyPermission "java.class.version" "read")
+(java.util.PropertyPermission "java.specification.name" "read")
+(java.util.PropertyPermission "java.vendor.url" "read")
+(java.util.PropertyPermission "java.vm.version" "read")
+(java.util.PropertyPermission "os.name" "read")
+(java.util.PropertyPermission "os.arch" "read")
+(java.util.PropertyPermission "os.version" "read")
+(java.util.PropertyPermission "java.version" "read")
+(java.util.PropertyPermission "java.vm.specification.version" "read")
+(java.util.PropertyPermission "java.vm.specification.name" "read")
+(java.util.PropertyPermission "java.specification.vendor" "read")
+(java.util.PropertyPermission "java.vm.vendor" "read")
+(java.util.PropertyPermission "file.separator" "read")
+(java.util.PropertyPermission "path.separator" "read")
+(java.util.PropertyPermission "java.vm.name" "read")
+(java.util.PropertyPermission "java.vm.specification.vendor" "read")
+(java.util.PropertyPermission "org.osgi.framework.*" "read")
+(java.io.FilePermission "" "read")
+(java.io.FilePermission "-" "read,write,delete")
+
+# Added for OSGi SP R3 - this likely is not needed any more
+(org.osgi.framework.PackagePermission "java.*" "import")
+# Added for OSGi R4.3
+(org.osgi.framework.CapabilityPermission "osgi.ee" "require")
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/profile/DefaultProfileLogger.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/profile/DefaultProfileLogger.java
new file mode 100644
index 000000000..00d60d93d
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/profile/DefaultProfileLogger.java
@@ -0,0 +1,391 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.profile;
+
+import java.io.*;
+import java.util.*;
+import org.eclipse.osgi.framework.debug.FrameworkDebugOptions;
+import org.eclipse.osgi.framework.internal.core.FrameworkProperties;
+
+public class DefaultProfileLogger implements ProfileLogger {
+ protected static final String DEFAULTPROFILE_PROP = "osgi.defaultprofile."; //$NON-NLS-1$
+ protected static final String PROP_FILENAME = DEFAULTPROFILE_PROP + "logfilename"; //$NON-NLS-1$
+ protected static final String PROP_LOGSYNCHRONOUSLY = DEFAULTPROFILE_PROP + "logsynchronously"; //$NON-NLS-1$
+ protected static final String PROP_BUFFERSIZE = DEFAULTPROFILE_PROP + "buffersize"; //$NON-NLS-1$
+
+ protected static final String DEFAULTPROFILE_OPTION = "org.eclipse.osgi/defaultprofile/"; //$NON-NLS-1$
+ protected static final String OPTION_FILENAME = DEFAULTPROFILE_OPTION + "logfilename"; //$NON-NLS-1$
+ protected static final String OPTION_LOGSYNCHRONOUSLY = DEFAULTPROFILE_OPTION + "logsynchronously"; //$NON-NLS-1$
+ protected static final String OPTION_BUFFERSIZE = DEFAULTPROFILE_OPTION + "buffersize"; //$NON-NLS-1$
+
+ protected boolean logSynchronously = false;
+ protected long startTime = 0;
+ protected static final int DEFAULT_BUFFER_SIZE = 256;
+
+ protected TimeEntry[] timeLogEntries = null;
+ protected int timeEntriesIndex = 0;
+ protected StringBuffer timelog = null;
+
+ protected long launchTime = -1;
+ protected int bufferSize = DEFAULT_BUFFER_SIZE;
+ protected String logFileName = null;
+ protected File logFile = null;
+ private StringBuffer entryReport = new StringBuffer(120);
+ private StringBuffer padsb = new StringBuffer(16); // to prevent creating this over and over
+ protected int indent;
+ protected int timePaddingLength;
+ protected Stack<AccumPerfScope> scopeStack;
+ protected Map<String, AccumPerfData> scopeToAccumPerfDataMap;
+
+ public DefaultProfileLogger() {
+ initProps();
+
+ int size = getBufferSize();
+ timeLogEntries = new TimeEntry[size];
+ timelog = new StringBuffer(4096);
+ for (int i = 0; i < size; i++) {
+ timeLogEntries[i] = timeEntryFactory();
+ }
+ timeEntriesIndex = 0;
+
+ launchTime = getLaunchTime();
+ if (launchTime == -1) {
+ startTime = getMainStartTime();
+ } else {
+ startTime = launchTime;
+ }
+
+ long freq = getTimerFrequency();
+ for (timePaddingLength = 3; freq > 9; timePaddingLength++) {
+ freq /= 10;
+ }
+
+ logInitMessages();
+ }
+
+ protected void logInitMessages() {
+ int index = 0;
+ if (launchTime != -1L) {
+ logTime(Profile.FLAG_NONE, "DefaultProfileLogger.init()", "launch time initialized", null); //$NON-NLS-1$//$NON-NLS-2$
+ timeLogEntries[index++].time = launchTime;
+ }
+
+ logTime(Profile.FLAG_NONE, "DefaultProfileLogger.init()", "start time initialized", null); //$NON-NLS-1$//$NON-NLS-2$
+ timeLogEntries[index++].time = getMainStartTime();
+ }
+
+ protected long getLaunchTime() {
+ String launchTimeString = FrameworkProperties.getProperty("launch.startMillis"); //$NON-NLS-1$
+ if (launchTimeString != null) {
+ return Long.parseLong(launchTimeString);
+ }
+ return -1L;
+ }
+
+ protected long getMainStartTime() {
+ String timeString = FrameworkProperties.getProperty("eclipse.startTime"); //$NON-NLS-1$
+ if (timeString != null)
+ return Long.parseLong(timeString);
+
+ return System.currentTimeMillis();
+ }
+
+ public void initProps() {
+ String prop;
+ FrameworkDebugOptions dbgOptions = null;
+ // if osgi.debug is not available, don't force DebugOptions
+ // to init as this variable may be set later on where
+ // DebugOptions will succeed.
+ if (FrameworkProperties.getProperty("osgi.debug") != null) { //$NON-NLS-1$
+ dbgOptions = FrameworkDebugOptions.getDefault();
+ if (dbgOptions != null) {
+ logFileName = dbgOptions.getOption(OPTION_FILENAME);
+ logSynchronously = dbgOptions.getBooleanOption(OPTION_LOGSYNCHRONOUSLY, false);
+ int size = dbgOptions.getIntegerOption(OPTION_BUFFERSIZE, 0);
+ if (size > 0)
+ bufferSize = size;
+ }
+ }
+
+ if ((prop = FrameworkProperties.getProperty(PROP_FILENAME)) != null) {
+ logFileName = prop;
+ if (dbgOptions != null)
+ dbgOptions.setOption(OPTION_FILENAME, logFileName);
+ }
+ if ((prop = FrameworkProperties.getProperty(PROP_LOGSYNCHRONOUSLY)) != null) {
+ logSynchronously = Boolean.valueOf(prop).booleanValue();
+ if (dbgOptions != null)
+ dbgOptions.setOption(OPTION_LOGSYNCHRONOUSLY, new Boolean(logSynchronously).toString());
+ }
+ if ((prop = FrameworkProperties.getProperty(PROP_BUFFERSIZE)) != null) {
+ try {
+ int value = Integer.parseInt(prop);
+ if (value > 0) {
+ bufferSize = value;
+ if (dbgOptions != null)
+ dbgOptions.setOption(OPTION_BUFFERSIZE, Integer.toString(bufferSize));
+ }
+ } catch (NumberFormatException e) {
+ // do nothing
+ }
+ }
+ }
+
+ public synchronized void logTime(int flag, String id, String msg, String description) {
+ if (timeEntriesIndex == timeLogEntries.length) {
+ makeLog();
+ logTime(Profile.FLAG_NONE, "Profile.logTime()", "log entries rolled", null); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ TimeEntry entry = timeLogEntries[timeEntriesIndex++];
+ entry.time = getTime();
+ entry.id = id;
+ entry.msg = msg;
+ entry.flag = flag;
+ entry.description = description;
+
+ if (logSynchronously) {
+ System.out.print(getProfileLog().substring(2));
+ }
+ }
+
+ public synchronized String getProfileLog() {
+ String log;
+ log = getProfileLogReport();
+ writeToProfileLogFile(log);
+ return log;
+ }
+
+ public synchronized void accumLogEnter(String scope) {
+ // Initialize our data structures
+ if (scopeStack == null)
+ scopeStack = new Stack<AccumPerfScope>();
+ if (scopeToAccumPerfDataMap == null)
+ scopeToAccumPerfDataMap = new TreeMap<String, AccumPerfData>();
+
+ // We want getTime() to evaluate as late as possible
+ scopeStack.push(new AccumPerfScope(scope, getTime()));
+ }
+
+ public synchronized void accumLogExit(String scope) {
+ // What time is it?
+ long exit = getTime();
+
+ // Initialize our data structures
+ if (scopeStack == null)
+ scopeStack = new Stack<AccumPerfScope>();
+ if (scopeToAccumPerfDataMap == null)
+ scopeToAccumPerfDataMap = new TreeMap<String, AccumPerfData>();
+
+ // Do our calculations
+ AccumPerfScope then = scopeStack.pop();
+ if (then == null)
+ System.err.println("ACCUM PERF ERROR: Scope stack empty: " + scope); //$NON-NLS-1$
+ else {
+ if (!then.scope.equals(scope))
+ System.err.println("ACCUM PERF ERROR: Scope mismatch: then='" + then.scope + "', now='" + scope + "'"); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
+
+ AccumPerfData now = scopeToAccumPerfDataMap.get(scope);
+ if (now == null) {
+ now = new AccumPerfData(scope);
+ scopeToAccumPerfDataMap.put(scope, now);
+ }
+
+ now.time += exit - then.enter;
+ now.enters++;
+ }
+ }
+
+ protected long getTime() {
+ return System.currentTimeMillis();
+ }
+
+ protected long getTimerFrequency() {
+ return 1000L; // millisecond
+ }
+
+ protected TimeEntry findCompareEntry(int index, String id, int flag) {
+ if (index > 0)
+ index--;
+ int prev = index;
+ if (flag != Profile.FLAG_ENTER) {
+ while (index >= 0) {
+ TimeEntry entry = timeLogEntries[index];
+ if (entry.id.equals(id)) {
+ switch (flag) {
+ case Profile.FLAG_NONE :
+ return entry;
+ case Profile.FLAG_EXIT :
+ if (entry.flag == Profile.FLAG_ENTER)
+ return entry;
+ break;
+ }
+ }
+ index--;
+ }
+ }
+ return timeLogEntries[prev];
+ }
+
+ protected String entryReport(TimeEntry entry, TimeEntry compareWith) {
+ // indent level:
+ entryReport.setLength(0);
+ if (entry.flag == Profile.FLAG_ENTER)
+ indent++;
+ long zeroTime = getRelativeTime(getStartTime());
+
+ entryReport.append('-');
+ long entryTime = getRelativeTime(entry.time);
+ long diff = entryTime - zeroTime;
+ entryReport.append(pad(Long.toString(diff), timePaddingLength));
+ entryReport.append(" :"); //$NON-NLS-1$
+ diff = entry.time - compareWith.time;
+ entryReport.append(pad(Long.toString(diff), timePaddingLength));
+ entryReport.append(pad("", indent * 2)); // indent before displaying the entry.id //$NON-NLS-1$
+
+ if (entry.flag == Profile.FLAG_ENTER)
+ entryReport.append(" >> "); //$NON-NLS-1$
+ else if (entry.flag == Profile.FLAG_EXIT)
+ entryReport.append(" << "); //$NON-NLS-1$
+ else if (entry.flag == Profile.FLAG_NONE)
+ entryReport.append(" -- "); //$NON-NLS-1$
+
+ entryReport.append(entry.id);
+ entryReport.append(" > "); //$NON-NLS-1$
+ entryReport.append(entry.msg);
+ if (entry.description != null) {
+ entryReport.append(" :: "); //$NON-NLS-1$
+ entryReport.append(entry.description);
+ }
+ entryReport.append("\r\n"); //$NON-NLS-1$
+
+ if (entry.flag == Profile.FLAG_EXIT)
+ indent -= 1;
+ return entryReport.toString();
+ }
+
+ protected String accumEntryReport(AccumPerfData d) {
+ return (" " + d.scope + ":enters=" + d.enters + ";time=" + d.time + ";\r\n"); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$//$NON-NLS-4$
+ }
+
+ protected void makeLog() {
+ indent = 0;
+ timelog.append("\r\n"); //$NON-NLS-1$
+ for (int i = 0; i < timeEntriesIndex; i++) {
+ TimeEntry entry = timeLogEntries[i];
+ TimeEntry cmpEntry = findCompareEntry(i, entry.id, entry.flag);
+ timelog.append(entryReport(entry, cmpEntry));
+ }
+ timeEntriesIndex = 0;
+
+ if (scopeToAccumPerfDataMap == null || scopeToAccumPerfDataMap.isEmpty())
+ return; // No data; nothing to do
+ timelog.append("\r\n"); //$NON-NLS-1$
+ timelog.append("Cumulative Log:\r\n"); //$NON-NLS-1$
+ for (AccumPerfData d : scopeToAccumPerfDataMap.values()) {
+ timelog.append(accumEntryReport(d));
+ }
+ scopeToAccumPerfDataMap.clear();
+ }
+
+ protected String pad(String str, int size) {
+ padsb.setLength(0);
+ int len = str.length();
+ int count = size - len;
+ for (int i = 0; i < count; i++)
+ padsb.append(' ');
+ padsb.append(str);
+ return padsb.toString();
+ }
+
+ protected String getProfileLogReport() {
+ if (timelog == null)
+ return ""; //$NON-NLS-1$
+ makeLog();
+ String log = timelog.toString();
+ timelog.setLength(0);
+ return log;
+ }
+
+ protected void writeToProfileLogFile(String log) {
+ File profileLog = getProfileLogFile();
+ if (profileLog == null)
+ return;
+ FileWriter fw = null;
+ try {
+ fw = new FileWriter(profileLog.getAbsolutePath(), true);
+ fw.write(log);
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ if (fw != null)
+ try {
+ fw.close();
+ } catch (IOException e) {
+ // do nothing
+ }
+ }
+ }
+
+ protected File getProfileLogFile() {
+ if (logFile == null)
+ if ((logFileName != null) && (logFileName.length() > 0))
+ logFile = new File(logFileName);
+ return logFile;
+ }
+
+ protected long getStartTime() {
+ return startTime;
+ }
+
+ protected long getRelativeTime(long absoluteTime) {
+ return absoluteTime;
+ }
+
+ protected int getBufferSize() {
+ if (bufferSize < 2)
+ return DEFAULT_BUFFER_SIZE;
+ return bufferSize;
+ }
+
+ protected TimeEntry timeEntryFactory() {
+ return new TimeEntry();
+ }
+
+ protected class TimeEntry {
+ public long time;
+ public String id;
+ public String msg;
+ public String description;
+ public int flag;
+ }
+
+ protected static class AccumPerfData {
+ public AccumPerfData(String scope) {
+ this.scope = scope;
+ }
+
+ public String scope;
+ public long time;
+ public long enters;
+ }
+
+ protected static class AccumPerfScope {
+ public AccumPerfScope(String scope, long enter) {
+ this.scope = scope;
+ this.enter = enter;
+ }
+
+ public String scope;
+ public long enter;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/profile/Profile.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/profile/Profile.java
new file mode 100644
index 000000000..aed3e7eca
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/profile/Profile.java
@@ -0,0 +1,268 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.profile;
+
+import org.eclipse.osgi.framework.debug.FrameworkDebugOptions;
+import org.eclipse.osgi.framework.internal.core.FrameworkProperties;
+
+/**
+ * This class is a development tool that provides a simple way to log
+ * programmer defined timmings for performance evaluations. This profiling
+ * allows logging of a timestamp with a corresponding message to a trace
+ * buffer.
+ */
+
+public class Profile {
+ /**
+ * Profiling is enabled and available.
+ */
+ public static final boolean PROFILE = true; // enable profile compiling
+ /**
+ * The logging state of <tt>STARTUP</tt> messages
+ */
+ public static boolean STARTUP = false; // enable startup profiling
+ /**
+ * The logging state of <tt>BENCHMARK</tt> messages
+ */
+ public static boolean BENCHMARK = false; // enable all benchmarking
+ /**
+ * The logging state of <tt>DEBUG</tt> messages
+ */
+ public static boolean DEBUG = false; // enable general debug profiling
+
+ private static final String OSGI_PROP = "osgi.profile."; //$NON-NLS-1$
+ private static final String PROP_STARTUP = OSGI_PROP + "startup"; //$NON-NLS-1$
+ private static final String PROP_BENCHMARK = OSGI_PROP + "benchmark"; //$NON-NLS-1$
+ private static final String PROP_DEBUG = OSGI_PROP + "debug"; //$NON-NLS-1$
+ private static final String PROP_IMPL = OSGI_PROP + "impl"; //$NON-NLS-1$
+
+ private static final String OSGI_OPTION = "org.eclipse.osgi/profile/"; //$NON-NLS-1$
+ private static final String OPTION_STARTUP = OSGI_OPTION + "startup"; //$NON-NLS-1$
+ private static final String OPTION_BENCHMARK = OSGI_OPTION + "benchmark"; //$NON-NLS-1$
+ private static final String OPTION_DEBUG = OSGI_OPTION + "debug"; //$NON-NLS-1$
+ private static final String OPTION_IMPL = OSGI_OPTION + "impl"; //$NON-NLS-1$
+
+ /**
+ * The default logging flag.
+ */
+ public static final int FLAG_NONE = 0;
+ /**
+ * The logging flag for <strong>method enter</strong>
+ */
+ public static final int FLAG_ENTER = 1;
+ /**
+ * The logging flag for <strong>method exit</strong>
+ */
+ public static final int FLAG_EXIT = 2;
+ /**
+ * The description for <strong>method enter</strong>
+ */
+ public static final String ENTER_DESCRIPTION = "enter"; //$NON-NLS-1$
+ /**
+ * The description for <strong>method exit</strong>
+ */
+ public static final String EXIT_DESCRIPTION = "exit"; //$NON-NLS-1$
+
+ private static ProfileLogger profileLogger = null;
+ private static String profileLoggerClassName = null;
+
+ static {
+ initProps();
+ }
+
+ /**
+ * Initialize/update profiling properties.
+ *
+ * If profiling properties are updated, this method is called to update
+ * the profile states.
+ */
+ public static void initProps() {
+ String prop;
+ FrameworkDebugOptions dbgOptions = null;
+
+ // if osgi.debug is not available, don't force DebugOptions
+ // to init as this variable may be set later on where
+ // DebugOptions will succeed.
+ if (FrameworkProperties.getProperty("osgi.debug") != null) { //$NON-NLS-1$
+ dbgOptions = FrameworkDebugOptions.getDefault();
+ if (dbgOptions != null) {
+ STARTUP = dbgOptions.getBooleanOption(OPTION_STARTUP, false);
+ BENCHMARK = dbgOptions.getBooleanOption(OPTION_BENCHMARK, false);
+ DEBUG = dbgOptions.getBooleanOption(OPTION_DEBUG, false);
+ if (profileLogger == null)
+ profileLoggerClassName = dbgOptions.getOption(OPTION_IMPL);
+ }
+ }
+
+ // System properties will always override anything in .options file
+ if ((prop = FrameworkProperties.getProperty(PROP_STARTUP)) != null) {
+ STARTUP = Boolean.valueOf(prop).booleanValue();
+ if (dbgOptions != null)
+ dbgOptions.setOption(OPTION_STARTUP, new Boolean(STARTUP).toString());
+ }
+ if ((prop = FrameworkProperties.getProperty(PROP_BENCHMARK)) != null) {
+ BENCHMARK = Boolean.valueOf(prop).booleanValue();
+ if (dbgOptions != null)
+ dbgOptions.setOption(OPTION_BENCHMARK, new Boolean(BENCHMARK).toString());
+ }
+ if ((prop = FrameworkProperties.getProperty(PROP_DEBUG)) != null) {
+ DEBUG = Boolean.valueOf(prop).booleanValue();
+ if (dbgOptions != null)
+ dbgOptions.setOption(OPTION_DEBUG, new Boolean(DEBUG).toString());
+ }
+
+ if (profileLogger == null) {
+ if ((prop = FrameworkProperties.getProperty(PROP_IMPL)) != null) {
+ profileLoggerClassName = prop;
+ if (dbgOptions != null)
+ dbgOptions.setOption(OPTION_IMPL, profileLoggerClassName);
+ }
+ } else {
+ profileLogger.initProps();
+ }
+ }
+
+ /**
+ * Log a method enter.
+ *
+ * @param id The method's unique identification (e.g. org.eclipse.class#name).
+ */
+ public static void logEnter(String id) {
+ logTime(FLAG_ENTER, id, ENTER_DESCRIPTION, null);
+ }
+
+ /**
+ * Log a method enter.
+ *
+ * @param id The method's unique identification (e.g. org.eclipse.class#name).
+ * @param description A description of the method.
+ */
+ public static void logEnter(String id, String description) {
+ logTime(FLAG_ENTER, id, ENTER_DESCRIPTION, description);
+ }
+
+ /**
+ * Log a method exit.
+ *
+ * @param id The method's unique identification (e.g. org.eclipse.class#name).
+ */
+ public static void logExit(String id) {
+ logTime(FLAG_EXIT, id, EXIT_DESCRIPTION, null);
+ }
+
+ /**
+ * Log a method exit.
+ *
+ * @param id The method's unique identification (e.g. org.eclipse.class#name).
+ * @param description A description of the method.
+ */
+ public static void logExit(String id, String description) {
+ logTime(FLAG_EXIT, id, EXIT_DESCRIPTION, description);
+ }
+
+ /**
+ * Log a message.
+ *
+ * @param id The method's unique identification (e.g. org.eclipse.class#name).
+ * @param msg The message.
+ */
+ public static void logTime(String id, String msg) {
+ logTime(FLAG_NONE, id, msg, null);
+ }
+
+ /**
+ * Log a message.
+ *
+ * @param id The method's unique identification (e.g. org.eclipse.class#name).
+ * @param msg The message.
+ * @param description A description of the method.
+ */
+ public static void logTime(String id, String msg, String description) {
+ logTime(FLAG_NONE, id, msg, description);
+ }
+
+ /**
+ * Log a message.
+ *
+ * @param flag A profile logging flag.
+ * @param id The method's unique identification (e.g. org.eclipse.class#name).
+ * @param msg The message.
+ * @param description A description of the method.
+ *
+ * @see #FLAG_ENTER
+ * @see #FLAG_EXIT
+ * @see #FLAG_NONE
+ */
+ public static void logTime(int flag, String id, String msg, String description) {
+ if (profileLogger == null)
+ profileLogger = createProfileLogger();
+ profileLogger.logTime(flag, id, msg, description);
+ }
+
+ /**
+ * Use cumulative logging to record the entrance from this scope.
+ *
+ * @param scope The entering scope
+ */
+ public static void accumLogEnter(String scope) {
+ if (profileLogger == null)
+ profileLogger = createProfileLogger();
+ profileLogger.accumLogEnter(scope);
+ }
+
+ /**
+ * Use cumulative logging to record the exit from this scope.
+ *
+ * @param scope The exiting scope
+ */
+ public static void accumLogExit(String scope) {
+ if (profileLogger == null)
+ profileLogger = createProfileLogger();
+ profileLogger.accumLogExit(scope);
+ }
+
+ /**
+ * Get the profiling log report and reset the trace buffer.
+ *
+ * @return The profiling log report.
+ */
+ public static String getProfileLog() {
+ if (profileLogger != null)
+ return profileLogger.getProfileLog();
+ return ""; //$NON-NLS-1$
+ }
+
+ /**
+ * Create an instance of the appropriate profile logger
+ */
+ private static ProfileLogger createProfileLogger() {
+ ProfileLogger result = null;
+
+ // Try to create it by class name
+ if (profileLoggerClassName != null) {
+ Class<?> profileImplClass = null;
+ try {
+ profileImplClass = Class.forName(profileLoggerClassName);
+ result = (ProfileLogger) profileImplClass.newInstance();
+ } catch (Exception e) {
+ // could not find the class
+ e.printStackTrace();
+ }
+ }
+
+ // Use the default
+ if (result == null)
+ result = new DefaultProfileLogger();
+
+ return (result);
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/profile/ProfileLogger.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/profile/ProfileLogger.java
new file mode 100644
index 000000000..31cab05ea
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/profile/ProfileLogger.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.profile;
+
+public interface ProfileLogger {
+
+ /**
+ *
+ *@see Profile#initProps()
+ */
+ public void initProps();
+
+ /**
+ *@see Profile#logTime(int, String, String, String)
+ */
+ public void logTime(int flag, String id, String msg, String description);
+
+ /**
+ * @see Profile#accumLogEnter(String)
+ */
+ public void accumLogEnter(String scope);
+
+ /**
+ * @see Profile#accumLogExit(String)
+ */
+ public void accumLogExit(String scope);
+
+ /**
+ *
+ * @see Profile#getProfileLog()
+ */
+ public String getProfileLog();
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/service/security/AuthorizationEngine.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/service/security/AuthorizationEngine.java
new file mode 100644
index 000000000..7cbb1effd
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/service/security/AuthorizationEngine.java
@@ -0,0 +1,88 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.provisional.service.security;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.eclipse.osgi.framework.eventmgr.*;
+import org.eclipse.osgi.signedcontent.SignedContent;
+import org.osgi.framework.BundleContext;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * An authorization engine is used to grant authorization to {@link SignedContent}.
+ * For example, an engine could determine if <code>SignedContent</code> is authorized
+ * to enable code from a signed bundle.
+ * @since 3.4
+ */
+public abstract class AuthorizationEngine {
+
+ private EventManager manager = new EventManager();
+ private EventDispatcher<AuthorizationListener, Object, AuthorizationEvent> dispatcher = new AuthEventDispatcher();
+ private final ServiceTracker<AuthorizationListener, AuthorizationListener> listenerTracker;
+
+ public AuthorizationEngine(BundleContext context) {
+ listenerTracker = new ServiceTracker<AuthorizationListener, AuthorizationListener>(context, AuthorizationListener.class.getName(), null);
+ listenerTracker.open();
+ }
+
+ /**
+ * Authorizes a <code>SignedContent</code> object. The engine determines if the
+ * signed content authorization should be granted. The context is the entity
+ * associated with the signed content. For example, signed content
+ * for a bundle will have a <code>Bundle</code> object as the context.
+ * @param content the signed content. The value may be <code>null</code>.
+ * @param context the context associated with the signed content. The value may be <code>null</code>.
+ */
+ public final void authorize(SignedContent content, Object context) {
+ fireEvent(doAuthorize(content, context));
+ }
+
+ private void fireEvent(AuthorizationEvent event) {
+ if (event == null)
+ return;
+ Object[] services = listenerTracker.getServices();
+ if (services == null)
+ return;
+ Map<AuthorizationListener, Object> listeners = new HashMap<AuthorizationListener, Object>();
+ for (Object service : services) {
+ listeners.put((AuthorizationListener) service, service);
+ }
+ ListenerQueue<AuthorizationListener, Object, AuthorizationEvent> queue = new ListenerQueue<AuthorizationListener, Object, AuthorizationEvent>(manager);
+ queue.queueListeners(listeners.entrySet(), dispatcher);
+ queue.dispatchEventSynchronous(0, event);
+ }
+
+ /**
+ * Authorizes a <code>SignedContent</code> object. The engine determines if the
+ * signed content authorization should be granted.
+ * @param content
+ * @param context the context associated with the signed content
+ * @return an authorization event which will be fired. A value of <code>null</code>
+ * may be returned; in this case no authorization event will be fired.
+ */
+ protected abstract AuthorizationEvent doAuthorize(SignedContent content, Object context);
+
+ /**
+ * Return the current status of the Authorization system.
+ *
+ * @return A value of {@link AuthorizationStatus#OK} or {@link AuthorizationStatus#ERROR}
+ * @see AuthorizationStatus#OK
+ * @see AuthorizationStatus#ERROR
+ */
+ abstract public int getStatus();
+
+ class AuthEventDispatcher implements EventDispatcher<AuthorizationListener, Object, AuthorizationEvent> {
+ public void dispatchEvent(AuthorizationListener eventListener, Object listenerObject, int eventAction, AuthorizationEvent eventObject) {
+ eventListener.authorizationEvent(eventObject);
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/service/security/AuthorizationEvent.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/service/security/AuthorizationEvent.java
new file mode 100644
index 000000000..23fb3da0b
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/service/security/AuthorizationEvent.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.provisional.service.security;
+
+import org.eclipse.osgi.signedcontent.SignedContent;
+
+/**
+ * An event that is fired when an AuthorizationEngine implementation makes
+ * a decision.
+ * @since 3.4
+ */
+public class AuthorizationEvent {
+
+ /**
+ * Result code meaning that the operation was allowed
+ */
+ public static final int ALLOWED = 0;
+
+ /**
+ * Result code meaning that the operation was denied
+ */
+ public static final int DENIED = 1;
+
+ private final int result;
+ private final SignedContent content;
+ private final Object context;
+ private final int severity;
+
+ /**
+ * Create a new AuthorizationEvent
+ * @param result - the result code
+ * @param content - the signed content
+ * @param context - operation specific context
+ * @param severity - severity code
+ */
+ public AuthorizationEvent(int result, SignedContent content, Object context, int severity) {
+ this.result = result;
+ this.content = content;
+ this.context = context;
+ this.severity = severity;
+ }
+
+ /**
+ * Get the result code
+ * @return - the result code
+ */
+ public int getResult() {
+ return result;
+ }
+
+ /**
+ * get the severity
+ * @return - the severity
+ */
+ public int getSeverity() {
+ return severity;
+ }
+
+ /**
+ * Get the SignedContent object being evaluated
+ * @return - SignedContent
+ */
+ public SignedContent getSignedContent() {
+ return content;
+ }
+
+ /**
+ * Get the operation specific context
+ * @return - context
+ */
+ public Object getContext() {
+ return context;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/service/security/AuthorizationListener.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/service/security/AuthorizationListener.java
new file mode 100644
index 000000000..9cc4f3fdd
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/service/security/AuthorizationListener.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.provisional.service.security;
+
+import java.util.EventListener;
+
+/**
+ * A Listener interface for an authorization handler. Implementors
+ * should register as an OSGI service.
+ * @since 3.4
+ */
+public interface AuthorizationListener extends EventListener {
+
+ /**
+ * Called when an AuthorizationEvent has occurred
+ */
+ public void authorizationEvent(AuthorizationEvent event);
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/service/security/AuthorizationStatus.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/service/security/AuthorizationStatus.java
new file mode 100644
index 000000000..0d7074d98
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/service/security/AuthorizationStatus.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.provisional.service.security;
+
+/**
+ * Constants for status codes in the Authorization engine.
+ * <p>
+ * This class is not intended to be extended by clients.
+ * </p>
+ *
+ * @since 3.4
+ */
+public class AuthorizationStatus {
+
+ /**
+ * This code means that the system is functioning normally - no bundles
+ * are currently experiencing authorization problems.
+ */
+ public static final int OK = 0x00;
+
+ /**
+ * This code means that there are bundles in the system that are being
+ * disabled due to authorization constraints.
+ */
+ public static final int ERROR = 0x01;
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/verifier/CertificateChain.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/verifier/CertificateChain.java
new file mode 100644
index 000000000..5ea55579e
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/verifier/CertificateChain.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.provisional.verifier;
+
+import java.security.cert.Certificate;
+import java.util.Date;
+
+/**
+ * This class represents a chain of certificates.
+ * <p>
+ * <strong>EXPERIMENTAL</strong>. This class or interface has been added as
+ * part of a work in progress. There is no guarantee that this API will
+ * work or that it will remain the same. Please do not use this API without
+ * consulting with the equinox team.
+ * </p>
+ */
+public interface CertificateChain {
+ /**
+ * Returns the list of X500 distinguished names that make up the certificate chain. Each
+ * distinguished name is separated by a ';'. The first distinguished name is the signer
+ * and the last is the root Certificate Authority.
+ * @return the list of X500 distinguished names that make up the certificate chain
+ */
+ public String getChain();
+
+ /**
+ * Retruns all certificates in this certificate chain
+ * @return all certificates in this certificate chain
+ */
+ public Certificate[] getCertificates();
+
+ /**
+ * Returns the first certificate of the certificate chain
+ * @return the first certificate of the certificate chain
+ */
+ public Certificate getSigner();
+
+ /**
+ * Returns the root certificate of the certificate chain
+ * @return the foot certificate of the certificate chain
+ */
+ public Certificate getRoot();
+
+ /**
+ * Returns true if this certificate chain is trusted
+ * @return true if this certificate chain is trusted
+ */
+ boolean isTrusted();
+
+ /**
+ * Return the signing time for this signer.
+ *
+ * @return null if there is a signing time for this signer null otherwise
+ */
+ public Date getSigningTime();
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/verifier/CertificateTrustAuthority.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/verifier/CertificateTrustAuthority.java
new file mode 100644
index 000000000..d418de8a3
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/verifier/CertificateTrustAuthority.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.provisional.verifier;
+
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+
+/**
+ * A CertificateTrustAuthority is used to check if certificate chains are trusted.
+ *
+ */
+public interface CertificateTrustAuthority {
+
+ /**
+ * Determines if the certificates are trusted. This method will throw a
+ * <code>CertificateException</code> if the specified certificate chain is not trusted.
+ * @param certChain a chain of certificates
+ * @throws CertificateException if the certficates are not trusted
+ */
+ public void checkTrust(Certificate[] certChain) throws CertificateException;
+
+ /**
+ * Add the specified certificate chain as a trusted certificate chain.
+ *
+ * @param certChain a chain of certificates
+ */
+ public void addTrusted(Certificate[] certChain) throws CertificateException;
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/verifier/CertificateVerifier.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/verifier/CertificateVerifier.java
new file mode 100644
index 000000000..63e40fd15
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/verifier/CertificateVerifier.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.provisional.verifier;
+
+import java.security.SignatureException;
+import java.security.cert.*;
+
+/**
+ * A certificate verifier is used to verify the authenticity of a signed
+ * repository. A certificate verifier is created using a
+ * {@link CertificateVerifierFactory}.
+ * <p>
+ * <strong>EXPERIMENTAL</strong>. This class or interface has been added as
+ * part of a work in progress. There is no guarantee that this API will
+ * work or that it will remain the same. Please do not use this API without
+ * consulting with the equinox team.
+ * </p>
+ */
+public interface CertificateVerifier {
+ /**
+ * Verify the content of the repository.
+ *
+ * @throws CertificateException
+ * @throws CertificateExpiredException
+ * @throws CertificateParsingException
+ * @throws SignatureException
+ */
+ public void checkContent() throws CertificateException, CertificateExpiredException, SignatureException;
+
+ /**
+ * Verifies the content of the repository. An array is returned with the entry names
+ * which are corrupt. If no entries are corrupt then an empty array is returned.
+ * @return An array of entry names which are corrupt. An empty array is returned if the
+ * repository is not corrupt or if the repository is not signed.
+ */
+ public String[] verifyContent();
+
+ /**
+ * Returns true if the repository is signed
+ * @return true if the repository is signed
+ */
+ public boolean isSigned();
+
+ /**
+ * Returns all certificate chains of the repository. All certificate chains
+ * are returned whether they are trusted or not. If the repository is not signed
+ * then an empty array is returned.
+ * @return all certificate chains of the repository
+ */
+ public CertificateChain[] getChains();
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/verifier/CertificateVerifierFactory.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/verifier/CertificateVerifierFactory.java
new file mode 100644
index 000000000..e36fb8198
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/provisional/verifier/CertificateVerifierFactory.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.provisional.verifier;
+
+import java.io.File;
+import java.io.IOException;
+import org.osgi.framework.Bundle;
+
+/**
+ * A factory used to create certificate verifiers.
+ * <p>
+ * <strong>EXPERIMENTAL</strong>. This class or interface has been added as
+ * part of a work in progress. There is no guarantee that this API will
+ * work or that it will remain the same. Please do not use this API without
+ * consulting with the equinox team.
+ * </p>
+ */
+public interface CertificateVerifierFactory {
+ /**
+ * Creates a certificate verifier for the specified content of a repository
+ * @param content the content of the repository
+ * @return a certificate verifier for the specified content of a repository
+ * @throws IOException if an IO exception occurs while reading the repository
+ */
+ public CertificateVerifier getVerifier(File content) throws IOException;
+
+ /**
+ * Returns a certificate verifier for the specified bundle.
+ * @param bundle the bundle to get a verifier for
+ * @return a certificate verifier for the specified bundle.
+ * @throws IOException if an IO exception occurs while reading the bundle content
+ */
+ public CertificateVerifier getVerifier(Bundle bundle) throws IOException;
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/service/security/DefaultAuthorizationEngine.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/service/security/DefaultAuthorizationEngine.java
new file mode 100644
index 000000000..4257bf0a4
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/service/security/DefaultAuthorizationEngine.java
@@ -0,0 +1,186 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.service.security;
+
+import java.io.*;
+import java.security.cert.CertificateException;
+import java.util.Properties;
+import org.eclipse.core.runtime.adaptor.LocationManager;
+import org.eclipse.osgi.baseadaptor.BaseData;
+import org.eclipse.osgi.framework.internal.core.AbstractBundle;
+import org.eclipse.osgi.framework.internal.core.FrameworkProperties;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+import org.eclipse.osgi.internal.baseadaptor.DevClassPathHelper;
+import org.eclipse.osgi.internal.provisional.service.security.*;
+import org.eclipse.osgi.internal.signedcontent.SignedBundleHook;
+import org.eclipse.osgi.internal.signedcontent.SignedStorageHook;
+import org.eclipse.osgi.service.resolver.*;
+import org.eclipse.osgi.signedcontent.SignedContent;
+import org.eclipse.osgi.signedcontent.SignerInfo;
+import org.osgi.framework.*;
+
+public class DefaultAuthorizationEngine extends AuthorizationEngine {
+
+ private static final String VERSION_PROP = "Version"; //$NON-NLS-1$
+ private static final String VERSION_NUM = "1.0"; //$NON-NLS-1$
+ private static final Version VERSION_MAX = new Version(2, 0, 0);
+
+ private final State systemState;
+ private final BundleContext bundleContext;
+
+ public static final int ENFORCE_NONE = 0x0000;
+ public static final int ENFORCE_SIGNED = 0x0001;
+ public static final int ENFORCE_TRUSTED = 0x0002;
+ public static final int ENFORCE_VALIDITY = 0x0004;
+
+ private static final String STR_ENFORCE_NONE = "any"; //$NON-NLS-1$
+ private static final String STR_ENFORCE_SIGNED = "signed"; //$NON-NLS-1$
+ private static final String STR_ENFORCE_TRUSTED = "trusted"; //$NON-NLS-1$
+ private static final String STR_ENFORCE_VALIDITY = "validity"; //$NON-NLS-1$
+
+ private static final String POLICY_NAME = "org.eclipse.equinox.security"; //$NON-NLS-1$
+ private static final String POLICY_PROP = "osgi.signedcontent.authorization.engine.policy"; //$NON-NLS-1$
+ private static final String FILE_LOAD_POLICY = ".loadpolicy"; //$NON-NLS-1$
+ private static int enforceFlags = 0;
+
+ private static final File policyFile;
+ static {
+ File osgiFile = LocationManager.getOSGiConfigurationDir();
+ policyFile = new File(osgiFile.getPath() + File.separatorChar + FILE_LOAD_POLICY);
+
+ Properties properties = null;
+ // load the policy file, if not exist, create it and load it
+ if (policyFile.exists()) {
+ try {
+ properties = new Properties();
+ properties.load(new FileInputStream(policyFile));
+ } catch (IOException e) {
+ SignedBundleHook.log("Error loading policy file", FrameworkLogEntry.ERROR, e); //$NON-NLS-1$
+ }
+ }
+
+ if (properties != null) {
+ Version version = new Version(0, 0, 0);
+ String versionProp = properties.getProperty(VERSION_PROP);
+ if (versionProp != null)
+ try {
+ version = new Version(versionProp);
+ } catch (IllegalArgumentException e) {
+ // do nothing;
+ }
+ if (VERSION_MAX.compareTo(version) > 0) {
+ String policy = properties.getProperty(POLICY_PROP);
+ if (policy != null)
+ try {
+ enforceFlags = Integer.parseInt(policy);
+ } catch (NumberFormatException e) {
+ // do nothing;
+ }
+ }
+ } else {
+ String policy = FrameworkProperties.getProperty(POLICY_PROP);
+ if (policy == null || STR_ENFORCE_NONE.equals(policy))
+ enforceFlags = ENFORCE_NONE;
+ else if (STR_ENFORCE_TRUSTED.equals(policy))
+ enforceFlags = ENFORCE_TRUSTED | ENFORCE_SIGNED;
+ else if (STR_ENFORCE_SIGNED.equals(policy))
+ enforceFlags = ENFORCE_SIGNED;
+ else if (STR_ENFORCE_VALIDITY.equals(policy))
+ enforceFlags = ENFORCE_TRUSTED | ENFORCE_SIGNED | ENFORCE_VALIDITY;
+ }
+
+ }
+
+ public DefaultAuthorizationEngine(BundleContext context, State systemState) {
+ super(context);
+ this.bundleContext = context;
+ this.systemState = systemState;
+ }
+
+ protected AuthorizationEvent doAuthorize(SignedContent content, Object context) {
+ boolean enabled = isEnabled(content, context);
+ AuthorizationEvent event = null;
+ if (context instanceof Bundle) {
+ BundleDescription desc = systemState.getBundle(((Bundle) context).getBundleId());
+ if (!enabled) {
+ DisabledInfo info = new DisabledInfo(POLICY_NAME, null, desc); // TODO add an error message
+ systemState.addDisabledInfo(info);
+ event = new AuthorizationEvent(AuthorizationEvent.DENIED, content, context, 0); // TODO severity??
+ } else {
+ DisabledInfo info = systemState.getDisabledInfo(desc, POLICY_NAME);
+ if (info != null) {
+ systemState.removeDisabledInfo(info);
+ }
+ event = new AuthorizationEvent(AuthorizationEvent.ALLOWED, content, context, 0);
+ }
+ }
+ return event;
+ }
+
+ private boolean isEnabled(SignedContent content, Object context) {
+ if (context instanceof Bundle && DevClassPathHelper.inDevelopmentMode()) {
+ String[] devClassPath = DevClassPathHelper.getDevClassPath(((Bundle) context).getSymbolicName());
+ if (devClassPath != null && devClassPath.length > 0)
+ return true; // always enabled bundles from workspace; they never are signed
+ }
+ if ((0 != (enforceFlags & ENFORCE_SIGNED)) && ((content == null) || !content.isSigned()))
+ return false;
+
+ SignerInfo[] signerInfos = content == null ? new SignerInfo[0] : content.getSignerInfos();
+ for (int i = 0; i < signerInfos.length; i++) {
+ if ((0 != (enforceFlags & ENFORCE_TRUSTED)) && !signerInfos[i].isTrusted())
+ return false;
+ if ((0 != (enforceFlags & ENFORCE_VALIDITY)))
+ try {
+ content.checkValidity(signerInfos[i]);
+ } catch (CertificateException e) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public int getStatus() {
+ if (0 != systemState.getDisabledBundles().length) {
+ return AuthorizationStatus.ERROR;
+ }
+ return AuthorizationStatus.OK;
+ }
+
+ public void processInstalledBundles() {
+ Bundle[] bundles = bundleContext.getBundles();
+ for (int i = 0; i < bundles.length; i++) {
+ BaseData baseData = (BaseData) ((AbstractBundle) bundles[i]).getBundleData();
+ SignedStorageHook hook = (SignedStorageHook) baseData.getStorageHook(SignedStorageHook.KEY);
+ SignedContent signedContent = hook != null ? hook.getSignedContent() : null;
+ authorize(signedContent, bundles[i]);
+ }
+ }
+
+ public void setLoadPolicy(int policy) {
+ if ((policy | ENFORCE_SIGNED | ENFORCE_TRUSTED | ENFORCE_VALIDITY) != (ENFORCE_SIGNED | ENFORCE_TRUSTED | ENFORCE_VALIDITY))
+ throw new IllegalArgumentException("Invalid policy: " + policy); //$NON-NLS-1$
+ enforceFlags = policy;
+ Properties properties = new Properties();
+ properties.setProperty(POLICY_PROP, Integer.toString(policy));
+ properties.setProperty(VERSION_PROP, VERSION_NUM); // need to act different when we have different versions
+ try {
+ properties.store(new FileOutputStream(policyFile), null);
+ } catch (IOException e) {
+ SignedBundleHook.log("Error saving load policy file", FrameworkLogEntry.ERROR, e); //$NON-NLS-1$
+ }
+ }
+
+ public int getLoadPolicy() {
+ return enforceFlags;
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/service/security/KeyStoreTrustEngine.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/service/security/KeyStoreTrustEngine.java
new file mode 100644
index 000000000..96b4bda67
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/service/security/KeyStoreTrustEngine.java
@@ -0,0 +1,335 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.service.security;
+
+import java.io.*;
+import java.security.*;
+import java.security.cert.*;
+import java.security.cert.Certificate;
+import java.util.*;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+import org.eclipse.osgi.internal.signedcontent.SignedBundleHook;
+import org.eclipse.osgi.internal.signedcontent.SignedContentMessages;
+import org.eclipse.osgi.service.security.TrustEngine;
+import org.eclipse.osgi.util.NLS;
+
+//*potential enhancements*
+// 1. reloading from the backing file when it changes
+// 3. methods to support lock/unlock
+// 3a. Using a callback handler to collect the password
+// 3b. managing lock/unlock between multiple threads. dealing with SWT UI thread
+// 4. methods to support changing password, etc
+// 5. methods to support export, etc
+// 6. 'friendly-name' generator
+// 7. Listeners for change events
+public class KeyStoreTrustEngine extends TrustEngine {
+
+ private KeyStore keyStore;
+
+ private final String type;
+ private final String path;
+ private final char[] password;
+ private final String name;
+
+ /**
+ * Create a new KeyStoreTrustEngine that is backed by a KeyStore
+ * @param path - path to the keystore
+ * @param type - the type of keystore at the path location
+ * @param password - the password required to unlock the keystore
+ */
+ public KeyStoreTrustEngine(String path, String type, char[] password, String name) { //TODO: This should be a *CallbackHandler*
+ this.path = path;
+ this.type = type;
+ this.password = password;
+ this.name = name;
+ }
+
+ /**
+ * Return the type
+ * @return type - the type for the KeyStore being managed
+ */
+ private String getType() {
+ return type;
+ }
+
+ /**
+ * Return the path
+ * @return - the path for the KeyStore being managed
+ */
+ private String getPath() {
+ return path;
+ }
+
+ /**
+ * Return the password
+ * @return password - the password as a char[]
+ */
+ private char[] getPassword() {
+ return password;
+ }
+
+ /**
+ * Return the KeyStore managed
+ * @return The KeyStore instance, initialized and loaded
+ * @throws KeyStoreException
+ */
+ private synchronized KeyStore getKeyStore() throws IOException, GeneralSecurityException {
+ if (null == keyStore) {
+ keyStore = KeyStore.getInstance(getType());
+ final InputStream in = getInputStream();
+ try {
+ loadStore(keyStore, in);
+ } finally {
+ try {
+ in.close();
+ } catch (IOException e) {
+ //ignore secondary failure
+ }
+ }
+ }
+
+ if (keyStore == null)
+ throw new KeyStoreException(NLS.bind(SignedContentMessages.Default_Trust_Keystore_Load_Failed, getPath()));
+
+ return keyStore;
+ }
+
+ public Certificate findTrustAnchor(Certificate[] certChain) throws IOException {
+
+ if (certChain == null || certChain.length == 0)
+ throw new IllegalArgumentException("Certificate chain is required"); //$NON-NLS-1$
+
+ try {
+ Certificate rootCert = null;
+ KeyStore store = getKeyStore();
+ for (int i = 0; i < certChain.length; i++) {
+ if (certChain[i] instanceof X509Certificate) {
+ if (i == certChain.length - 1) {
+ // this is the last certificate in the chain
+ // determine if we have a valid root
+ X509Certificate cert = (X509Certificate) certChain[i];
+ if (cert.getSubjectDN().equals(cert.getIssuerDN())) {
+ cert.verify(cert.getPublicKey());
+ rootCert = cert; // this is a self-signed certificate
+ } else {
+ // try to find a parent, we have an incomplete chain
+ return findAlternativeRoot(cert, store);
+ }
+ } else {
+ X509Certificate nextX509Cert = (X509Certificate) certChain[i + 1];
+ certChain[i].verify(nextX509Cert.getPublicKey());
+ }
+ }
+
+ synchronized (store) {
+ String alias = rootCert == null ? null : store.getCertificateAlias(rootCert);
+ if (alias != null)
+ return store.getCertificate(alias);
+ else if (rootCert != certChain[i]) {
+ alias = store.getCertificateAlias(certChain[i]);
+ if (alias != null)
+ return store.getCertificate(alias);
+ }
+ // if we have reached the end and the last cert is not found to be a valid root CA
+ // then we need to back off the root CA and try to find an alternative
+ if (certChain.length > 1 && i == certChain.length - 1 && certChain[i - 1] instanceof X509Certificate)
+ return findAlternativeRoot((X509Certificate) certChain[i - 1], store);
+ }
+ }
+ } catch (KeyStoreException e) {
+ throw (IOException) new IOException(e.getMessage()).initCause(e);
+ } catch (GeneralSecurityException e) {
+ SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.WARNING, e);
+ return null;
+ }
+ return null;
+ }
+
+ private Certificate findAlternativeRoot(X509Certificate cert, KeyStore store) throws InvalidKeyException, KeyStoreException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException, CertificateException {
+ synchronized (store) {
+ for (Enumeration<String> e = store.aliases(); e.hasMoreElements();) {
+ Certificate nextCert = store.getCertificate(e.nextElement());
+ if (nextCert instanceof X509Certificate && ((X509Certificate) nextCert).getSubjectDN().equals(cert.getIssuerDN())) {
+ cert.verify(nextCert.getPublicKey());
+ return nextCert;
+ }
+ }
+ return null;
+ }
+ }
+
+ protected String doAddTrustAnchor(Certificate cert, String alias) throws IOException, GeneralSecurityException {
+ if (isReadOnly())
+ throw new IOException(SignedContentMessages.Default_Trust_Read_Only);
+ if (cert == null) {
+ throw new IllegalArgumentException("Certificate must be specified"); //$NON-NLS-1$
+ }
+ try {
+ KeyStore store = getKeyStore();
+ synchronized (store) {
+ String oldAlias = store.getCertificateAlias(cert);
+ if (null != oldAlias)
+ throw new CertificateException(SignedContentMessages.Default_Trust_Existing_Cert);
+ Certificate oldCert = store.getCertificate(alias);
+ if (null != oldCert)
+ throw new CertificateException(SignedContentMessages.Default_Trust_Existing_Alias);
+ store.setCertificateEntry(alias, cert);
+ final OutputStream out = getOutputStream();
+ try {
+ saveStore(store, out);
+ } finally {
+ safeClose(out);
+ }
+ }
+ } catch (KeyStoreException ke) {
+ throw (CertificateException) new CertificateException(ke.getMessage()).initCause(ke);
+ }
+ return alias;
+ }
+
+ protected void doRemoveTrustAnchor(Certificate cert) throws IOException, GeneralSecurityException {
+ if (isReadOnly())
+ throw new IOException(SignedContentMessages.Default_Trust_Read_Only);
+ if (cert == null) {
+ throw new IllegalArgumentException("Certificate must be specified"); //$NON-NLS-1$
+ }
+ try {
+ KeyStore store = getKeyStore();
+ synchronized (store) {
+ String alias = store.getCertificateAlias(cert);
+ if (alias == null) {
+ throw new CertificateException(SignedContentMessages.Default_Trust_Cert_Not_Found);
+ }
+ removeTrustAnchor(alias);
+ }
+ } catch (KeyStoreException ke) {
+ throw (CertificateException) new CertificateException(ke.getMessage()).initCause(ke);
+ }
+ }
+
+ protected void doRemoveTrustAnchor(String alias) throws IOException, GeneralSecurityException {
+
+ if (alias == null) {
+ throw new IllegalArgumentException("Alias must be specified"); //$NON-NLS-1$
+ }
+ try {
+ KeyStore store = getKeyStore();
+ synchronized (store) {
+ Certificate oldCert = store.getCertificate(alias);
+ if (oldCert == null)
+ throw new CertificateException(SignedContentMessages.Default_Trust_Cert_Not_Found);
+ store.deleteEntry(alias);
+ final OutputStream out = getOutputStream();
+ try {
+ saveStore(store, out);
+ } finally {
+ safeClose(out);
+ }
+ }
+ } catch (KeyStoreException ke) {
+ throw (CertificateException) new CertificateException(ke.getMessage()).initCause(ke);
+ }
+ }
+
+ public Certificate getTrustAnchor(String alias) throws IOException, GeneralSecurityException {
+
+ if (alias == null) {
+ throw new IllegalArgumentException("Alias must be specified"); //$NON-NLS-1$
+ }
+
+ try {
+ KeyStore store = getKeyStore();
+ synchronized (store) {
+ return store.getCertificate(alias);
+ }
+ } catch (KeyStoreException ke) {
+ throw (CertificateException) new CertificateException(ke.getMessage()).initCause(ke);
+ }
+ }
+
+ public String[] getAliases() throws IOException, GeneralSecurityException {
+
+ List<String> returnList = new ArrayList<String>();
+ try {
+ KeyStore store = getKeyStore();
+ synchronized (store) {
+ for (Enumeration<String> aliases = store.aliases(); aliases.hasMoreElements();) {
+ String currentAlias = aliases.nextElement();
+ if (store.isCertificateEntry(currentAlias)) {
+ returnList.add(currentAlias);
+ }
+ }
+ }
+ } catch (KeyStoreException ke) {
+ throw (CertificateException) new CertificateException(ke.getMessage()).initCause(ke);
+ }
+ return returnList.toArray(new String[] {});
+ }
+
+ /**
+ * Load using the current password
+ */
+ private void loadStore(KeyStore store, InputStream is) throws IOException, GeneralSecurityException {
+ store.load(is, getPassword());
+ }
+
+ /**
+ * Save using the current password
+ */
+ private void saveStore(KeyStore store, OutputStream os) throws IOException, GeneralSecurityException {
+ store.store(os, getPassword());
+ }
+
+ /**
+ * Closes a stream and ignores any resulting exception. This is useful
+ * when doing stream cleanup in a finally block where secondary exceptions
+ * are not worth logging.
+ */
+ private void safeClose(OutputStream out) {
+ try {
+ if (out != null)
+ out.close();
+ } catch (IOException e) {
+ //ignore
+ }
+ }
+
+ /**
+ * Get an input stream for the KeyStore managed
+ * @return inputstream - the stream
+ * @throws KeyStoreException
+ */
+ private InputStream getInputStream() throws IOException {
+ return new FileInputStream(new File(getPath()));
+ }
+
+ /**
+ * Get an output stream for the KeyStore managed
+ * @return outputstream - the stream
+ * @throws KeyStoreException
+ */
+ private OutputStream getOutputStream() throws IOException {
+
+ File file = new File(getPath());
+ if (!file.exists())
+ file.createNewFile();
+
+ return new FileOutputStream(file);
+ }
+
+ public boolean isReadOnly() {
+ return getPassword() == null || !(new File(path).canWrite());
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/FilteredServiceListener.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/FilteredServiceListener.java
new file mode 100644
index 000000000..528d8d2e6
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/FilteredServiceListener.java
@@ -0,0 +1,205 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.serviceregistry;
+
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.framework.internal.core.BundleContextImpl;
+import org.eclipse.osgi.framework.internal.core.FilterImpl;
+import org.osgi.framework.*;
+import org.osgi.framework.hooks.service.ListenerHook;
+
+/**
+ * Service Listener delegate.
+ */
+class FilteredServiceListener implements ServiceListener, ListenerHook.ListenerInfo {
+ /** Filter for listener. */
+ private final FilterImpl filter;
+ /** Real listener. */
+ private final ServiceListener listener;
+ /** The bundle context */
+ private final BundleContextImpl context;
+ /** is this an AllServiceListener */
+ private final boolean allservices;
+ /** is this an UnfilteredServiceListener */
+ private final boolean unfiltered;
+ /** an objectClass required by the filter */
+ private final String objectClass;
+ /** indicates whether the listener has been removed */
+ private volatile boolean removed;
+
+ /**
+ * Constructor.
+ *
+ * @param context The bundle context of the bundle which added the specified service listener.
+ * @param filterstring The filter string specified when this service listener was added.
+ * @param listener The service listener object.
+ * @exception InvalidSyntaxException if the filter is invalid.
+ */
+ FilteredServiceListener(final BundleContextImpl context, final ServiceListener listener, final String filterstring) throws InvalidSyntaxException {
+ this.unfiltered = (listener instanceof UnfilteredServiceListener);
+ if (filterstring == null) {
+ this.filter = null;
+ this.objectClass = null;
+ } else {
+ FilterImpl filterImpl = FilterImpl.newInstance(filterstring);
+ String clazz = filterImpl.getRequiredObjectClass();
+ if (unfiltered || (clazz == null)) {
+ this.objectClass = null;
+ this.filter = filterImpl;
+ } else {
+ this.objectClass = clazz.intern(); /*intern the name for future identity comparison */
+ this.filter = filterstring.equals(getObjectClassFilterString(this.objectClass)) ? null : filterImpl;
+ }
+ }
+ this.removed = false;
+ this.listener = listener;
+ this.context = context;
+ this.allservices = (listener instanceof AllServiceListener);
+ }
+
+ /**
+ * Receives notification that a service has had a lifecycle change.
+ *
+ * @param event The <code>ServiceEvent</code> object.
+ */
+ public void serviceChanged(ServiceEvent event) {
+ ServiceReferenceImpl<?> reference = (ServiceReferenceImpl<?>) event.getServiceReference();
+
+ // first check if we can short circuit the filter match if the required objectClass does not match the event
+ objectClassCheck: if (objectClass != null) {
+ String[] classes = reference.getClasses();
+ int size = classes.length;
+ for (int i = 0; i < size; i++) {
+ if (classes[i] == objectClass) // objectClass strings have previously been interned for identity comparison
+ break objectClassCheck;
+ }
+ return; // no class in this event matches a required part of the filter; we do not need to deliver this event
+ }
+ // TODO could short circuit service.id filters as well since the id is constant for a registration.
+
+ if (!ServiceRegistry.hasListenServicePermission(event, context))
+ return;
+
+ if (Debug.DEBUG_EVENTS) {
+ String listenerName = this.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(this)); //$NON-NLS-1$
+ Debug.println("filterServiceEvent(" + listenerName + ", \"" + getFilter() + "\", " + reference.getRegistration().getProperties() + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ }
+
+ event = filterMatch(event);
+ if (event == null) {
+ return;
+ }
+ if (allservices || ServiceRegistry.isAssignableTo(context, reference)) {
+ if (Debug.DEBUG_EVENTS) {
+ String listenerName = listener.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(listener)); //$NON-NLS-1$
+ Debug.println("dispatchFilteredServiceEvent(" + listenerName + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ listener.serviceChanged(event);
+ }
+ }
+
+ /**
+ * Returns a service event that should be delivered to the listener based on the filter evaluation.
+ * This may result in a service event of type MODIFIED_ENDMATCH.
+ *
+ * @param delivered The service event delivered by the framework.
+ * @return The event to be delivered or null if no event is to be delivered to the listener.
+ */
+ private ServiceEvent filterMatch(ServiceEvent delivered) {
+ boolean modified = delivered.getType() == ServiceEvent.MODIFIED;
+ ServiceEvent event = modified ? ((ModifiedServiceEvent) delivered).getModifiedEvent() : delivered;
+ if (unfiltered || (filter == null)) {
+ return event;
+ }
+ ServiceReference<?> reference = event.getServiceReference();
+ if (filter.match(reference)) {
+ return event;
+ }
+ if (modified) {
+ ModifiedServiceEvent modifiedServiceEvent = (ModifiedServiceEvent) delivered;
+ if (modifiedServiceEvent.matchPreviousProperties(filter)) {
+ return modifiedServiceEvent.getModifiedEndMatchEvent();
+ }
+ }
+ // does not match and did not match previous properties; do not send event
+ return null;
+ }
+
+ /**
+ * The string representation of this Filtered listener.
+ *
+ * @return The string representation of this listener.
+ */
+ public String toString() {
+ String filterString = getFilter();
+ if (filterString == null) {
+ filterString = ""; //$NON-NLS-1$
+ }
+ return listener.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(listener)) + filterString; //$NON-NLS-1$
+ }
+
+ /**
+ * Return the bundle context for the ListenerHook.
+ * @return The context of the bundle which added the service listener.
+ * @see org.osgi.framework.hooks.service.ListenerHook.ListenerInfo#getBundleContext()
+ */
+ public BundleContext getBundleContext() {
+ return context;
+ }
+
+ /**
+ * Return the filter string for the ListenerHook.
+ * @return The filter string with which the listener was added. This may
+ * be <code>null</code> if the listener was added without a filter.
+ * @see org.osgi.framework.hooks.service.ListenerHook.ListenerInfo#getFilter()
+ */
+ public String getFilter() {
+ if (filter != null) {
+ return filter.toString();
+ }
+ return getObjectClassFilterString(objectClass);
+ }
+
+ /**
+ * Return the state of the listener for this addition and removal life
+ * cycle. Initially this method will return <code>false</code>
+ * indicating the listener has been added but has not been removed.
+ * After the listener has been removed, this method must always return
+ * <code>true</code>.
+ *
+ * @return <code>false</code> if the listener has not been been removed,
+ * <code>true</code> otherwise.
+ */
+ public boolean isRemoved() {
+ return removed;
+ }
+
+ /**
+ * Mark the service listener registration as removed.
+ */
+ void markRemoved() {
+ removed = true;
+ }
+
+ /**
+ * Returns an objectClass filter string for the specified class name.
+ * @return A filter string for the specified class name or <code>null</code> if the
+ * specified class name is <code>null</code>.
+ */
+ private static String getObjectClassFilterString(String className) {
+ if (className == null) {
+ return null;
+ }
+ return "(" + Constants.OBJECTCLASS + "=" + className + ")"; //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/HookContext.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/HookContext.java
new file mode 100644
index 000000000..74610341a
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/HookContext.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (c) 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.serviceregistry;
+
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * A callable hook that contains the context for call a collection of hooks.
+ * This is effectively a "closure" for calling each hook. The hook context
+ * must know the type of the hook object, the method to call on the hook
+ * as well as all the parameters which need to be passed to the hook method.
+ *
+ */
+public interface HookContext {
+
+ /**
+ * Call the specified hook.
+ *
+ * @param hook The hook object to call. The hook object must be of the type
+ * supported by this hook context. If it is not, then this method will
+ * simply return.
+ * @param hookRegistration the registration for the hook object
+ * @throws Exception An exception thrown by the hook object.
+ */
+ public void call(Object hook, ServiceRegistration<?> hookRegistration) throws Exception;
+
+ /**
+ * Return the class name of the hook type supported by this hook context.
+ *
+ * @return The class name of the hook type supported by this hook context.
+ */
+ public String getHookClassName();
+
+ /**
+ * Return the hook method name called by this hook context.
+ *
+ * @return The hook method name called by this hook context.
+ */
+ public String getHookMethodName();
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ModifiedServiceEvent.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ModifiedServiceEvent.java
new file mode 100644
index 000000000..b3e62c2af
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ModifiedServiceEvent.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.serviceregistry;
+
+import org.osgi.framework.*;
+
+/**
+ * ServiceEvent subtype for MODIFIED_ENDMATCH computation.
+ *
+ */
+class ModifiedServiceEvent extends ServiceEvent {
+ private static final long serialVersionUID = -5373850978543026102L;
+ private final ServiceEvent modified;
+ private final ServiceEvent modifiedEndMatch;
+ private final ServiceProperties previousProperties;
+
+ /**
+ * Create a ServiceEvent containing the service properties prior to modification.
+ *
+ * @param reference Reference to service with modified properties.
+ * @param previousProperties Service properties prior to modification.
+ */
+ ModifiedServiceEvent(ServiceReference<?> reference, ServiceProperties previousProperties) {
+ super(ServiceEvent.MODIFIED, reference);
+ this.modified = new ServiceEvent(ServiceEvent.MODIFIED, reference);
+ this.modifiedEndMatch = new ServiceEvent(ServiceEvent.MODIFIED_ENDMATCH, reference);
+ this.previousProperties = previousProperties;
+ }
+
+ /**
+ * Return the service event of type MODIFIED.
+ *
+ * @return The service event of type MODIFIED.
+ */
+ ServiceEvent getModifiedEvent() {
+ return modified;
+ }
+
+ /**
+ * Return the service event of type MODIFIED_ENDMATCH.
+ *
+ * @return The service event of type MODIFIED_ENDMATCH.
+ */
+ ServiceEvent getModifiedEndMatchEvent() {
+ return modifiedEndMatch;
+ }
+
+ /**
+ * Return if the specified filter matches the previous service
+ * properties.
+ *
+ * @param filter The filer to evaluate using the previous service
+ * properties.
+ * @return True is the filter matches the previous service properties.
+ */
+ boolean matchPreviousProperties(Filter filter) {
+ /* We use matchCase here since ServiceProperties already
+ * does case insensitive lookup.
+ */
+ return filter.matchCase(previousProperties);
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ServiceProperties.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ServiceProperties.java
new file mode 100644
index 000000000..a829d2697
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ServiceProperties.java
@@ -0,0 +1,186 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.serviceregistry;
+
+import java.lang.reflect.Array;
+import java.util.*;
+import org.eclipse.osgi.framework.util.Headers;
+import org.osgi.framework.Constants;
+
+/**
+ * Hashtable for service properties.
+ *
+ * Supports case-insensitive key lookup.
+ */
+class ServiceProperties extends Headers<String, Object> {
+ /**
+ * Create a properties object for the service.
+ *
+ * @param props The properties for this service.
+ */
+ private ServiceProperties(int size, Dictionary<String, ?> props) {
+ super(size);
+
+ if (props == null) {
+ return;
+ }
+ synchronized (props) {
+ Enumeration<?> keysEnum = props.keys();
+
+ while (keysEnum.hasMoreElements()) {
+ Object key = keysEnum.nextElement();
+
+ if (key instanceof String) {
+ String header = (String) key;
+
+ setProperty(header, props.get(header));
+ }
+ }
+ }
+ }
+
+ /**
+ * Create a properties object for the service.
+ *
+ * @param props The properties for this service.
+ */
+ ServiceProperties(Dictionary<String, ?> props) {
+ this((props == null) ? 2 : props.size() + 2, props);
+ }
+
+ /**
+ * Get a clone of the value of a service's property.
+ *
+ * @param key header name.
+ * @return Clone of the value of the property or <code>null</code> if there is
+ * no property by that name.
+ */
+ Object getProperty(String key) {
+ return cloneValue(get(key));
+ }
+
+ /**
+ * Get the list of key names for the service's properties.
+ *
+ * @return The list of property key names.
+ */
+ synchronized String[] getPropertyKeys() {
+ int size = size();
+
+ String[] keynames = new String[size];
+
+ Enumeration<String> keysEnum = keys();
+
+ for (int i = 0; i < size; i++) {
+ keynames[i] = keysEnum.nextElement();
+ }
+
+ return keynames;
+ }
+
+ /**
+ * Put a clone of the property value into this property object.
+ *
+ * @param key Name of property.
+ * @param value Value of property.
+ * @return previous property value.
+ */
+ synchronized Object setProperty(String key, Object value) {
+ return set(key, cloneValue(value));
+ }
+
+ /**
+ * Attempt to clone the value if necessary and possible.
+ *
+ * For some strange reason, you can test to see of an Object is
+ * Cloneable but you can't call the clone method since it is
+ * protected on Object!
+ *
+ * @param value object to be cloned.
+ * @return cloned object or original object if we didn't clone it.
+ */
+ private static Object cloneValue(Object value) {
+ if (value == null)
+ return null;
+ if (value instanceof String) /* shortcut String */
+ return value;
+ if (value instanceof Number) /* shortcut Number */
+ return value;
+ if (value instanceof Character) /* shortcut Character */
+ return value;
+ if (value instanceof Boolean) /* shortcut Boolean */
+ return value;
+
+ Class<?> clazz = value.getClass();
+ if (clazz.isArray()) {
+ // Do an array copy
+ Class<?> type = clazz.getComponentType();
+ int len = Array.getLength(value);
+ Object clonedArray = Array.newInstance(type, len);
+ System.arraycopy(value, 0, clonedArray, 0, len);
+ return clonedArray;
+ }
+ // must use reflection because Object clone method is protected!!
+ try {
+ return clazz.getMethod("clone", (Class<?>[]) null).invoke(value, (Object[]) null); //$NON-NLS-1$
+ } catch (Exception e) {
+ /* clone is not a public method on value's class */
+ } catch (Error e) {
+ /* JCL does not support reflection; try some well known types */
+ if (value instanceof Vector<?>)
+ return ((Vector<?>) value).clone();
+ if (value instanceof Hashtable<?, ?>)
+ return ((Hashtable<?, ?>) value).clone();
+ }
+ return value;
+ }
+
+ public synchronized String toString() {
+ String keys[] = getPropertyKeys();
+
+ int size = keys.length;
+
+ StringBuffer sb = new StringBuffer(20 * size);
+
+ sb.append('{');
+
+ int n = 0;
+ for (int i = 0; i < size; i++) {
+ String key = keys[i];
+ if (!key.equals(Constants.OBJECTCLASS)) {
+ if (n > 0)
+ sb.append(", "); //$NON-NLS-1$
+
+ sb.append(key);
+ sb.append('=');
+ Object value = get(key);
+ if (value.getClass().isArray()) {
+ sb.append('[');
+ int length = Array.getLength(value);
+ for (int j = 0; j < length; j++) {
+ if (j > 0)
+ sb.append(',');
+ sb.append(Array.get(value, j));
+ }
+ sb.append(']');
+ } else {
+ sb.append(value);
+ }
+ n++;
+ }
+ }
+
+ sb.append('}');
+
+ return sb.toString();
+ }
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ServiceReferenceImpl.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ServiceReferenceImpl.java
new file mode 100644
index 000000000..e6784ca0a
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ServiceReferenceImpl.java
@@ -0,0 +1,275 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.serviceregistry;
+
+import org.osgi.framework.*;
+
+/**
+ * A reference to a service.
+ *
+ * <p>
+ * The Framework returns <code>ServiceReference</code> objects from the
+ * <code>BundleContext.getServiceReference</code> and
+ * <code>BundleContext.getServiceReferences</code> methods.
+ * <p>
+ * A <code>ServiceReference</code> object may be shared between bundles and
+ * can be used to examine the properties of the service and to get the service
+ * object.
+ * <p>
+ * Every service registered in the Framework has a unique
+ * <code>ServiceRegistration</code> object and may have multiple, distinct
+ * <code>ServiceReference</code> objects referring to it.
+ * <code>ServiceReference</code> objects associated with a
+ * <code>ServiceRegistration</code> object have the same <code>hashCode</code>
+ * and are considered equal (more specifically, their <code>equals()</code>
+ * method will return <code>true</code> when compared).
+ * <p>
+ * If the same service object is registered multiple times,
+ * <code>ServiceReference</code> objects associated with different
+ * <code>ServiceRegistration</code> objects are not equal.
+ *
+ * @see BundleContext#getServiceReference
+ * @see BundleContext#getServiceReferences
+ * @see BundleContext#getService
+ * @ThreadSafe
+ */
+public class ServiceReferenceImpl<S> implements ServiceReference<S> {
+ /** Registered Service object. */
+ private final ServiceRegistrationImpl<S> registration;
+
+ /**
+ * Construct a reference.
+ *
+ */
+ ServiceReferenceImpl(ServiceRegistrationImpl<S> registration) {
+ this.registration = registration;
+ /* We must not dereference registration in the constructor
+ * since it is "leaked" to us in the ServiceRegistrationImpl
+ * constructor.
+ */
+ }
+
+ /**
+ * Returns the property value to which the specified property key is mapped
+ * in the properties <code>Dictionary</code> object of the service
+ * referenced by this <code>ServiceReference</code> object.
+ *
+ * <p>
+ * Property keys are case-insensitive.
+ *
+ * <p>
+ * This method must continue to return property values after the service has
+ * been unregistered. This is so references to unregistered services (for
+ * example, <code>ServiceReference</code> objects stored in the log) can
+ * still be interrogated.
+ *
+ * @param key The property key.
+ * @return The property value to which the key is mapped; <code>null</code>
+ * if there is no property named after the key.
+ */
+ public Object getProperty(String key) {
+ return registration.getProperty(key);
+ }
+
+ /**
+ * Returns an array of the keys in the properties <code>Dictionary</code>
+ * object of the service referenced by this <code>ServiceReference</code>
+ * object.
+ *
+ * <p>
+ * This method will continue to return the keys after the service has been
+ * unregistered. This is so references to unregistered services (for
+ * example, <code>ServiceReference</code> objects stored in the log) can
+ * still be interrogated.
+ *
+ * <p>
+ * This method is <i>case-preserving </i>; this means that every key in the
+ * returned array must have the same case as the corresponding key in the
+ * properties <code>Dictionary</code> that was passed to the
+ * {@link BundleContext#registerService(String[],Object,java.util.Dictionary)}
+ * or {@link ServiceRegistration#setProperties} methods.
+ *
+ * @return An array of property keys.
+ */
+ public String[] getPropertyKeys() {
+ return registration.getPropertyKeys();
+ }
+
+ /**
+ * Returns the bundle that registered the service referenced by this
+ * <code>ServiceReference</code> object.
+ *
+ * <p>
+ * This method must return <code>null</code> when the service has been
+ * unregistered. This can be used to determine if the service has been
+ * unregistered.
+ *
+ * @return The bundle that registered the service referenced by this
+ * <code>ServiceReference</code> object; <code>null</code> if
+ * that service has already been unregistered.
+ * @see BundleContext#registerService(String[],Object,java.util.Dictionary)
+ */
+ public Bundle getBundle() {
+ return registration.getBundle();
+ }
+
+ /**
+ * Returns the bundles that are using the service referenced by this
+ * <code>ServiceReference</code> object. Specifically, this method returns
+ * the bundles whose usage count for that service is greater than zero.
+ *
+ * @return An array of bundles whose usage count for the service referenced
+ * by this <code>ServiceReference</code> object is greater than
+ * zero; <code>null</code> if no bundles are currently using that
+ * service.
+ *
+ * @since 1.1
+ */
+ public Bundle[] getUsingBundles() {
+ return registration.getUsingBundles();
+ }
+
+ /**
+ * Tests if the bundle that registered the service referenced by this
+ * <code>ServiceReference</code> and the specified bundle use the same
+ * source for the package of the specified class name.
+ * <p>
+ * This method performs the following checks:
+ * <ol>
+ * <li>Get the package name from the specified class name.</li>
+ * <li>For the bundle that registered the service referenced by this
+ * <code>ServiceReference</code> (registrant bundle); find the source for
+ * the package. If no source is found then return <code>true</code> if the
+ * registrant bundle is equal to the specified bundle; otherwise return
+ * <code>false</code>.</li>
+ * <li>If the package source of the registrant bundle is equal to the
+ * package source of the specified bundle then return <code>true</code>;
+ * otherwise return <code>false</code>.</li>
+ * </ol>
+ *
+ * @param bundle The <code>Bundle</code> object to check.
+ * @param className The class name to check.
+ * @return <code>true</code> if the bundle which registered the service
+ * referenced by this <code>ServiceReference</code> and the
+ * specified bundle use the same source for the package of the
+ * specified class name. Otherwise <code>false</code> is returned.
+ *
+ * @since 1.3
+ */
+ public boolean isAssignableTo(Bundle bundle, String className) {
+ return registration.isAssignableTo(bundle, className);
+ }
+
+ /**
+ * Compares this <code>ServiceReference</code> with the specified
+ * <code>ServiceReference</code> for order.
+ *
+ * <p>
+ * If this <code>ServiceReference</code> and the specified
+ * <code>ServiceReference</code> have the same
+ * {@link Constants#SERVICE_ID service id} they are equal. This
+ * <code>ServiceReference</code> is less than the specified
+ * <code>ServiceReference</code> if it has a lower
+ * {@link Constants#SERVICE_RANKING service ranking} and greater if it has a
+ * higher service ranking. Otherwise, if this <code>ServiceReference</code>
+ * and the specified <code>ServiceReference</code> have the same
+ * {@link Constants#SERVICE_RANKING service ranking}, this
+ * <code>ServiceReference</code> is less than the specified
+ * <code>ServiceReference</code> if it has a higher
+ * {@link Constants#SERVICE_ID service id} and greater if it has a lower
+ * service id.
+ *
+ * @param object The <code>ServiceReference</code> to be compared.
+ * @return Returns a negative integer, zero, or a positive integer if this
+ * <code>ServiceReference</code> is less than, equal to, or
+ * greater than the specified <code>ServiceReference</code>.
+ * @since 1.4
+ */
+ public int compareTo(Object object) {
+ ServiceRegistrationImpl<?> other = ((ServiceReferenceImpl<?>) object).registration;
+
+ final int thisRanking = registration.getRanking();
+ final int otherRanking = other.getRanking();
+ if (thisRanking != otherRanking) {
+ if (thisRanking < otherRanking) {
+ return -1;
+ }
+ return 1;
+ }
+ final long thisId = registration.getId();
+ final long otherId = other.getId();
+ if (thisId == otherId) {
+ return 0;
+ }
+ if (thisId < otherId) {
+ return 1;
+ }
+ return -1;
+ }
+
+ /**
+ * Returns a hash code value for the object.
+ *
+ * @return a hash code value for this object.
+ */
+ public int hashCode() {
+ return registration.hashCode();
+ }
+
+ /**
+ * Indicates whether some other object is "equal to" this one.
+ *
+ * @param obj the reference object with which to compare.
+ * @return <code>true</code> if this object is the same as the obj
+ * argument; <code>false</code> otherwise.
+ */
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+
+ if (!(obj instanceof ServiceReferenceImpl<?>)) {
+ return false;
+ }
+
+ ServiceReferenceImpl<?> other = (ServiceReferenceImpl<?>) obj;
+
+ return registration == other.registration;
+ }
+
+ /**
+ * Return a string representation of this reference.
+ *
+ * @return String
+ */
+ public String toString() {
+ return registration.toString();
+ }
+
+ /**
+ * Return the ServiceRegistrationImpl for this ServiceReferenceImpl.
+ *
+ * @return The ServiceRegistrationImpl for this ServiceReferenceImpl.
+ */
+ public ServiceRegistrationImpl<S> getRegistration() {
+ return registration;
+ }
+
+ /**
+ * Return the classes under which the referenced service was registered.
+ *
+ * @return array of class names.
+ */
+ String[] getClasses() {
+ return registration.getClasses();
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ServiceRegistrationImpl.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ServiceRegistrationImpl.java
new file mode 100644
index 000000000..3821f9f53
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ServiceRegistrationImpl.java
@@ -0,0 +1,650 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.serviceregistry;
+
+import java.util.*;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.framework.internal.core.*;
+import org.osgi.framework.*;
+import org.osgi.framework.Constants;
+
+/**
+ * A registered service.
+ *
+ * The framework returns a ServiceRegistration object when a
+ * {@link BundleContextImpl#registerService(String, Object, Dictionary) BundleContext.registerService}
+ * method is successful. This object is for the private use of
+ * the registering bundle and should not be shared with other bundles.
+ * <p>The ServiceRegistration object may be used to update the properties
+ * for the service or to unregister the service.
+ *
+ * <p>If the ServiceRegistration is garbage collected the framework may remove
+ * the service. This implies that if a
+ * bundle wants to keep its service registered, it should keep the
+ * ServiceRegistration object referenced.
+ *
+ * @ThreadSafe
+ */
+public class ServiceRegistrationImpl<S> implements ServiceRegistration<S>, Comparable<ServiceRegistrationImpl<?>> {
+ /** Internal framework object. */
+ private final Framework framework;
+
+ private final ServiceRegistry registry;
+
+ /** context which registered this service. */
+ private final BundleContextImpl context;
+
+ /** bundle which registered this service. */
+ private final Bundle bundle;
+
+ /** service classes for this registration. */
+ private final String[] clazzes;
+
+ /** service object for this registration. */
+ private final S service;
+
+ /** Reference to this registration. */
+ /* @GuardedBy("registrationLock") */
+ private ServiceReferenceImpl<S> reference;
+
+ /** List of contexts using the service.
+ * List&lt;BundleContextImpl&gt;.
+ * */
+ /* @GuardedBy("registrationLock") */
+ private final List<BundleContextImpl> contextsUsing;
+
+ /** properties for this registration. */
+ /* @GuardedBy("registrationLock") */
+ private ServiceProperties properties;
+
+ /** service id. */
+ private final long serviceid;
+
+ /** service ranking. */
+ /* @GuardedBy("registrationLock") */
+ private int serviceranking;
+
+ /* internal object to use for synchronization */
+ private final Object registrationLock = new Object();
+
+ /** The registration state */
+ /* @GuardedBy("registrationLock") */
+ private int state;
+ private static final int REGISTERED = 0x00;
+ private static final int UNREGISTERING = 0x01;
+ private static final int UNREGISTERED = 0x02;
+
+ /**
+ * Construct a ServiceRegistration and register the service
+ * in the framework's service registry.
+ *
+ */
+ ServiceRegistrationImpl(ServiceRegistry registry, BundleContextImpl context, String[] clazzes, S service) {
+ this.registry = registry;
+ this.context = context;
+ this.bundle = context.getBundleImpl();
+ this.framework = context.getFramework();
+ this.clazzes = clazzes; /* must be set before calling createProperties. */
+ this.service = service;
+ this.serviceid = registry.getNextServiceId(); /* must be set before calling createProperties. */
+ this.contextsUsing = new ArrayList<BundleContextImpl>(10);
+
+ synchronized (registrationLock) {
+ this.state = REGISTERED;
+ /* We leak this from the constructor here, but it is ok
+ * because the ServiceReferenceImpl constructor only
+ * stores the value in a final field without
+ * otherwise using it.
+ */
+ this.reference = new ServiceReferenceImpl<S>(this);
+ }
+ }
+
+ /**
+ * Call after constructing this object to complete the registration.
+ */
+ void register(Dictionary<String, ?> props) {
+ final ServiceReferenceImpl<S> ref;
+ synchronized (registry) {
+ context.checkValid();
+ synchronized (registrationLock) {
+ ref = reference; /* used to publish event outside sync */
+ this.properties = createProperties(props); /* must be valid after unregister is called. */
+ }
+ if (Debug.DEBUG_SERVICES) {
+ Debug.println("registerService[" + bundle + "](" + this + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ registry.addServiceRegistration(context, this);
+ }
+
+ /* must not hold the registrations lock when this event is published */
+ registry.publishServiceEvent(new ServiceEvent(ServiceEvent.REGISTERED, ref));
+ }
+
+ /**
+ * Update the properties associated with this service.
+ *
+ * <p>The key "objectClass" cannot be modified by this method. It's
+ * value is set when the service is registered.
+ *
+ * <p>The following steps are followed to modify a service's properties:
+ * <ol>
+ * <li>The service's properties are replaced with the provided properties.
+ * <li>A {@link ServiceEvent} of type {@link ServiceEvent#MODIFIED}
+ * is synchronously sent.
+ * </ol>
+ *
+ * @param props The properties for this service.
+ * Changes should not be made to this object after calling this method.
+ * To update the service's properties this method should be called again.
+ * @exception java.lang.IllegalStateException If
+ * this ServiceRegistration has already been unregistered.
+ *
+ * @exception IllegalArgumentException If the <tt>properties</tt>
+ * parameter contains case variants of the same key name.
+ */
+ public void setProperties(Dictionary<String, ?> props) {
+ final ServiceReferenceImpl<S> ref;
+ final ServiceProperties previousProperties;
+ synchronized (registry) {
+ synchronized (registrationLock) {
+ if (state != REGISTERED) { /* in the process of unregisterING */
+ throw new IllegalStateException(Msg.SERVICE_ALREADY_UNREGISTERED_EXCEPTION);
+ }
+
+ ref = reference; /* used to publish event outside sync */
+ previousProperties = this.properties;
+ this.properties = createProperties(props);
+ }
+ registry.modifyServiceRegistration(context, this);
+ }
+ /* must not hold the registrationLock when this event is published */
+ registry.publishServiceEvent(new ModifiedServiceEvent(ref, previousProperties));
+ }
+
+ /**
+ * Unregister the service.
+ * Remove a service registration from the framework's service
+ * registry.
+ * All {@link ServiceReferenceImpl} objects for this registration
+ * can no longer be used to interact with the service.
+ *
+ * <p>The following steps are followed to unregister a service:
+ * <ol>
+ * <li>The service is removed from the framework's service
+ * registry so that it may no longer be used.
+ * {@link ServiceReferenceImpl}s for the service may no longer be used
+ * to get a service object for the service.
+ * <li>A {@link ServiceEvent} of type {@link ServiceEvent#UNREGISTERING}
+ * is synchronously sent so that bundles using this service
+ * may release their use of the service.
+ * <li>For each bundle whose use count for this service is greater
+ * than zero:
+ * <ol>
+ * <li>The bundle's use count for this service is set to zero.
+ * <li>If the service was registered with a {@link ServiceFactory},
+ * the {@link ServiceFactory#ungetService ServiceFactory.ungetService} method
+ * is called to release the service object for the bundle.
+ * </ol>
+ * </ol>
+ *
+ * @exception java.lang.IllegalStateException If
+ * this ServiceRegistration has already been unregistered.
+ * @see BundleContextImpl#ungetService
+ */
+ public void unregister() {
+ final ServiceReferenceImpl<S> ref;
+ synchronized (registry) {
+ synchronized (registrationLock) {
+ if (state != REGISTERED) { /* in the process of unregisterING */
+ throw new IllegalStateException(Msg.SERVICE_ALREADY_UNREGISTERED_EXCEPTION);
+ }
+
+ /* remove this object from the service registry */
+ if (Debug.DEBUG_SERVICES) {
+ Debug.println("unregisterService[" + bundle + "](" + this + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ registry.removeServiceRegistration(context, this);
+
+ state = UNREGISTERING; /* mark unregisterING */
+ ref = reference; /* used to publish event outside sync */
+ }
+ }
+
+ /* must not hold the registrationLock when this event is published */
+ registry.publishServiceEvent(new ServiceEvent(ServiceEvent.UNREGISTERING, ref));
+
+ int size = 0;
+ BundleContextImpl[] users = null;
+
+ synchronized (registrationLock) {
+ /* we have published the ServiceEvent, now mark the service fully unregistered */
+ state = UNREGISTERED;
+
+ size = contextsUsing.size();
+ if (size > 0) {
+ if (Debug.DEBUG_SERVICES) {
+ Debug.println("unregisterService: releasing users"); //$NON-NLS-1$
+ }
+ users = contextsUsing.toArray(new BundleContextImpl[size]);
+ }
+ }
+
+ /* must not hold the registrationLock while releasing services */
+ for (int i = 0; i < size; i++) {
+ releaseService(users[i]);
+ }
+
+ synchronized (registrationLock) {
+ contextsUsing.clear();
+
+ reference = null; /* mark registration dead */
+ }
+
+ /* The properties field must remain valid after unregister completes. */
+ }
+
+ /**
+ * Returns a {@link ServiceReferenceImpl} object for this registration.
+ * The {@link ServiceReferenceImpl} object may be shared with other bundles.
+ *
+ * @exception java.lang.IllegalStateException If
+ * this ServiceRegistration has already been unregistered.
+ * @return A {@link ServiceReferenceImpl} object.
+ */
+ public ServiceReference<S> getReference() {
+ return getReferenceImpl();
+ }
+
+ ServiceReferenceImpl<S> getReferenceImpl() {
+ /* use reference instead of unregistered so that ServiceFactorys, called
+ * by releaseService after the registration is unregistered, can
+ * get the ServiceReference. Note this technically may violate the spec
+ * but makes more sense.
+ */
+ synchronized (registrationLock) {
+ if (reference == null) {
+ throw new IllegalStateException(Msg.SERVICE_ALREADY_UNREGISTERED_EXCEPTION);
+ }
+
+ return reference;
+ }
+ }
+
+ /**
+ * Construct a properties object from the dictionary for this
+ * ServiceRegistration.
+ *
+ * @param p The properties for this service.
+ * @return A Properties object for this ServiceRegistration.
+ */
+ /* @GuardedBy("registrationLock") */
+ private ServiceProperties createProperties(Dictionary<String, ?> p) {
+ assert Thread.holdsLock(registrationLock);
+ ServiceProperties props = new ServiceProperties(p);
+
+ props.set(Constants.OBJECTCLASS, clazzes, true);
+ props.set(Constants.SERVICE_ID, new Long(serviceid), true);
+ props.setReadOnly();
+ Object ranking = props.getProperty(Constants.SERVICE_RANKING);
+
+ serviceranking = (ranking instanceof Integer) ? ((Integer) ranking).intValue() : 0;
+
+ return props;
+ }
+
+ /**
+ * Return the properties object. This is for framework internal use only.
+ * @return The service registration's properties.
+ */
+ public ServiceProperties getProperties() {
+ synchronized (registrationLock) {
+ return properties;
+ }
+ }
+
+ /**
+ * Get the value of a service's property.
+ *
+ * <p>This method will continue to return property values after the
+ * service has been unregistered. This is so that references to
+ * unregistered service can be interrogated.
+ * (For example: ServiceReference objects stored in the log.)
+ *
+ * @param key Name of the property.
+ * @return Value of the property or <code>null</code> if there is
+ * no property by that name.
+ */
+ Object getProperty(String key) {
+ synchronized (registrationLock) {
+ return properties.getProperty(key);
+ }
+ }
+
+ /**
+ * Get the list of key names for the service's properties.
+ *
+ * <p>This method will continue to return the keys after the
+ * service has been unregistered. This is so that references to
+ * unregistered service can be interrogated.
+ * (For example: ServiceReference objects stored in the log.)
+ *
+ * @return The list of property key names.
+ */
+ String[] getPropertyKeys() {
+ synchronized (registrationLock) {
+ return properties.getPropertyKeys();
+ }
+ }
+
+ /**
+ * Return the service id for this service.
+ * @return The service id for this service.
+ */
+ long getId() {
+ return serviceid;
+ }
+
+ /**
+ * Return the service ranking for this service.
+ * @return The service ranking for this service.
+ */
+ int getRanking() {
+ synchronized (registrationLock) {
+ return serviceranking;
+ }
+ }
+
+ String[] getClasses() {
+ return clazzes;
+ }
+
+ S getServiceObject() {
+ return service;
+ }
+
+ /**
+ * Return the bundle which registered the service.
+ *
+ * <p>This method will always return <code>null</code> when the
+ * service has been unregistered. This can be used to
+ * determine if the service has been unregistered.
+ *
+ * @return The bundle which registered the service.
+ */
+ Bundle getBundle() {
+ synchronized (registrationLock) {
+ if (reference == null) {
+ return null;
+ }
+
+ return bundle;
+ }
+ }
+
+ /**
+ * This method returns the bundle which registered the
+ * service regardless of the registration status of this
+ * service registration. This is not an OSGi specified
+ * method.
+ * @return The bundle which registered the service.
+ */
+ public Bundle getRegisteringBundle() {
+ return bundle;
+ }
+
+ Object getSafeService(BundleContextImpl user) {
+ try {
+ return getService(user);
+ } catch (IllegalStateException e) {
+ // can happen if the user is stopped on another thread
+ return null;
+ }
+ }
+
+ /**
+ * Get a service object for the using BundleContext.
+ *
+ * @param user BundleContext using service.
+ * @return Service object
+ */
+ Object getService(BundleContextImpl user) {
+ synchronized (registrationLock) {
+ if (state == UNREGISTERED) { /* service unregistered */
+ return null;
+ }
+ }
+ if (Debug.DEBUG_SERVICES) {
+ Debug.println("getService[" + user.getBundleImpl() + "](" + this + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ Map<ServiceRegistrationImpl<?>, ServiceUse<?>> servicesInUse = user.getServicesInUseMap();
+ if (servicesInUse == null) { /* user is closed */
+ user.checkValid(); /* throw exception */
+ }
+ /* Use a while loop to support retry if a call to a ServiceFactory fails */
+ while (true) {
+ ServiceUse<?> use;
+ boolean added = false;
+ /* Obtain the ServiceUse object for this service by bundle user */
+ synchronized (servicesInUse) {
+ user.checkValid();
+ use = servicesInUse.get(this);
+ if (use == null) {
+ /* if this is the first use of the service
+ * optimistically record this service is being used. */
+ use = new ServiceUse<S>(user, this);
+ added = true;
+ synchronized (registrationLock) {
+ if (state == UNREGISTERED) { /* service unregistered */
+ return null;
+ }
+ servicesInUse.put(this, use);
+ contextsUsing.add(user);
+ }
+ }
+ }
+
+ /* Obtain and return the service object */
+ synchronized (use) {
+ /* if another thread removed the ServiceUse, then
+ * go back to the top and start again */
+ synchronized (servicesInUse) {
+ user.checkValid();
+ if (servicesInUse.get(this) != use) {
+ continue;
+ }
+ }
+ Object serviceObject = use.getService();
+ /* if the service factory failed to return an object and
+ * we created the service use, then remove the
+ * optimistically added ServiceUse. */
+ if ((serviceObject == null) && added) {
+ synchronized (servicesInUse) {
+ synchronized (registrationLock) {
+ servicesInUse.remove(this);
+ contextsUsing.remove(user);
+ }
+ }
+ }
+ return serviceObject;
+ }
+ }
+ }
+
+ /**
+ * Unget a service for the using BundleContext.
+ *
+ * @param user BundleContext using service.
+ * @return <code>false</code> if the context bundle's use count for the service
+ * is zero or if the service has been unregistered,
+ * otherwise <code>true</code>.
+ */
+ boolean ungetService(BundleContextImpl user) {
+ synchronized (registrationLock) {
+ if (state == UNREGISTERED) {
+ return false;
+ }
+ }
+
+ if (Debug.DEBUG_SERVICES) {
+ Debug.println("ungetService[" + user.getBundleImpl() + "](" + this + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ Map<ServiceRegistrationImpl<?>, ServiceUse<?>> servicesInUse = user.getServicesInUseMap();
+ if (servicesInUse == null) {
+ return false;
+ }
+
+ ServiceUse<?> use;
+ synchronized (servicesInUse) {
+ use = servicesInUse.get(this);
+ if (use == null) {
+ return false;
+ }
+ }
+
+ synchronized (use) {
+ if (use.ungetService()) {
+ /* use count is now zero */
+ synchronized (servicesInUse) {
+ synchronized (registrationLock) {
+ servicesInUse.remove(this);
+ contextsUsing.remove(user);
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Release the service for the using BundleContext.
+ *
+ * @param user BundleContext using service.
+ */
+ void releaseService(BundleContextImpl user) {
+ synchronized (registrationLock) {
+ if (reference == null) { /* registration dead */
+ return;
+ }
+ }
+
+ if (Debug.DEBUG_SERVICES) {
+ Debug.println("releaseService[" + user.getBundleImpl() + "](" + this + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ Map<ServiceRegistrationImpl<?>, ServiceUse<?>> servicesInUse = user.getServicesInUseMap();
+ if (servicesInUse == null) {
+ return;
+ }
+ ServiceUse<?> use;
+ synchronized (servicesInUse) {
+ synchronized (registrationLock) {
+ use = servicesInUse.remove(this);
+ if (use == null) {
+ return;
+ }
+ contextsUsing.remove(user);
+ }
+ }
+ synchronized (use) {
+ use.releaseService();
+ }
+ }
+
+ /**
+ * Return the list of bundle which are using this service.
+ *
+ * @return Array of Bundles using this service.
+ */
+ Bundle[] getUsingBundles() {
+ synchronized (registrationLock) {
+ if (state == UNREGISTERED) /* service unregistered */
+ return null;
+
+ int size = contextsUsing.size();
+ if (size == 0)
+ return null;
+
+ /* Copy list of BundleContext into an array of Bundle. */
+ Bundle[] bundles = new Bundle[size];
+ for (int i = 0; i < size; i++)
+ bundles[i] = contextsUsing.get(i).getBundleImpl();
+
+ return bundles;
+ }
+ }
+
+ boolean isAssignableTo(Bundle client, String className) {
+ return framework.isServiceAssignableTo(bundle, client, className, service.getClass());
+ }
+
+ /**
+ * Return a String representation of this object.
+ *
+ * @return String representation of this object.
+ */
+ public String toString() {
+ int size = clazzes.length;
+ StringBuffer sb = new StringBuffer(50 * size);
+
+ sb.append('{');
+
+ for (int i = 0; i < size; i++) {
+ if (i > 0) {
+ sb.append(", "); //$NON-NLS-1$
+ }
+ sb.append(clazzes[i]);
+ }
+
+ sb.append("}="); //$NON-NLS-1$
+ sb.append(getProperties().toString());
+
+ return sb.toString();
+ }
+
+ /**
+ * Compares this <code>ServiceRegistrationImpl</code> with the specified
+ * <code>ServiceRegistrationImpl</code> for order.
+ *
+ * <p>
+ * This does a reverse comparison so that the highest item is sorted to the left.
+ * We keep ServiceRegistationImpls in sorted lists such that the highest
+ * ranked service is at element 0 for quick retrieval.
+ *
+ * @param other The <code>ServiceRegistrationImpl</code> to be compared.
+ * @return Returns a negative integer, zero, or a positive integer if this
+ * <code>ServiceRegistrationImpl</code> is greater than, equal to, or
+ * less than the specified <code>ServiceRegistrationImpl</code>.
+ */
+ public int compareTo(ServiceRegistrationImpl<?> other) {
+ final int thisRanking = this.getRanking();
+ final int otherRanking = other.getRanking();
+ if (thisRanking != otherRanking) {
+ if (thisRanking < otherRanking) {
+ return 1;
+ }
+ return -1;
+ }
+ final long thisId = this.getId();
+ final long otherId = other.getId();
+ if (thisId == otherId) {
+ return 0;
+ }
+ if (thisId < otherId) {
+ return -1;
+ }
+ return 1;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ServiceRegistry.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ServiceRegistry.java
new file mode 100644
index 000000000..a80b00b2e
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ServiceRegistry.java
@@ -0,0 +1,1362 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.serviceregistry;
+
+import java.security.*;
+import java.util.*;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.framework.eventmgr.*;
+import org.eclipse.osgi.framework.internal.core.*;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.*;
+import org.osgi.framework.Constants;
+import org.osgi.framework.hooks.service.*;
+import org.osgi.framework.hooks.service.ListenerHook.ListenerInfo;
+
+/**
+ * The Service Registry. This class is the main control point for service
+ * layer operations in the framework.
+ *
+ * @ThreadSafe
+ */
+public class ServiceRegistry {
+ public static final int SERVICEEVENT = 3;
+
+ static final String findHookName = FindHook.class.getName();
+ static final String eventHookName = EventHook.class.getName();
+ static final String eventListenerHookName = EventListenerHook.class.getName();
+ static final String listenerHookName = ListenerHook.class.getName();
+
+ /** Published services by class name.
+ * The {@literal List<ServiceRegistrationImpl<?>>}s are both sorted
+ * in the natural order of ServiceRegistrationImpl and also are sets in that
+ * there must be no two entries in a List which are equal.
+ */
+ /* @GuardedBy("this") */
+ private final Map<String, List<ServiceRegistrationImpl<?>>> publishedServicesByClass;
+
+ /** All published services.
+ * The List is both sorted in the natural order of ServiceRegistrationImpl and also is a
+ * set in that there must be no two entries in the List which are equal.
+ */
+ /* @GuardedBy("this") */
+ private final List<ServiceRegistrationImpl<?>> allPublishedServices;
+
+ /** Published services by BundleContextImpl.
+ * The {@literal List<ServiceRegistrationImpl<?>>}s are NOT sorted
+ * and also are sets in that
+ * there must be no two entries in a List which are equal.
+ */
+ /* @GuardedBy("this") */
+ private final Map<BundleContextImpl, List<ServiceRegistrationImpl<?>>> publishedServicesByContext;
+
+ /** next free service id. */
+ /* @GuardedBy("this") */
+ private long serviceid;
+
+ /** Active Service Listeners.
+ * {@literal Map<BundleContextImpl,CopyOnWriteIdentityMap<ServiceListener,FilteredServiceListener>>}.
+ */
+ /* @GuardedBy("serviceEventListeners") */
+ private final Map<BundleContextImpl, CopyOnWriteIdentityMap<ServiceListener, FilteredServiceListener>> serviceEventListeners;
+
+ /** initial capacity of the main data structure */
+ private static final int initialCapacity = 50;
+ /** initial capacity of the nested data structure */
+ private static final int initialSubCapacity = 10;
+ /** framework which created this service registry */
+ private final Framework framework;
+
+ /**
+ * Initializes the internal data structures of this ServiceRegistry.
+ *
+ */
+ public ServiceRegistry(Framework framework) {
+ this.framework = framework;
+ serviceid = 1;
+ publishedServicesByClass = new HashMap<String, List<ServiceRegistrationImpl<?>>>(initialCapacity);
+ publishedServicesByContext = new HashMap<BundleContextImpl, List<ServiceRegistrationImpl<?>>>(initialCapacity);
+ allPublishedServices = new ArrayList<ServiceRegistrationImpl<?>>(initialCapacity);
+ serviceEventListeners = new HashMap<BundleContextImpl, CopyOnWriteIdentityMap<ServiceListener, FilteredServiceListener>>(initialCapacity);
+ }
+
+ /**
+ * Registers the specified service object with the specified properties
+ * under the specified class names into the Framework. A
+ * <code>ServiceRegistrationImpl</code> object is returned. The
+ * <code>ServiceRegistrationImpl</code> object is for the private use of the
+ * bundle registering the service and should not be shared with other
+ * bundles. The registering bundle is defined to be the context bundle.
+ * Other bundles can locate the service by using either the
+ * {@link #getServiceReferences} or {@link #getServiceReference} method.
+ *
+ * <p>
+ * A bundle can register a service object that implements the
+ * {@link ServiceFactory} interface to have more flexibility in providing
+ * service objects to other bundles.
+ *
+ * <p>
+ * The following steps are required to register a service:
+ * <ol>
+ * <li>If <code>service</code> is not a <code>ServiceFactory</code>,
+ * an <code>IllegalArgumentException</code> is thrown if
+ * <code>service</code> is not an <code>instanceof</code> all the
+ * classes named.
+ * <li>The Framework adds these service properties to the specified
+ * <code>Dictionary</code> (which may be <code>null</code>): a property
+ * named {@link Constants#SERVICE_ID} identifying the registration number of
+ * the service and a property named {@link Constants#OBJECTCLASS} containing
+ * all the specified classes. If any of these properties have already been
+ * specified by the registering bundle, their values will be overwritten by
+ * the Framework.
+ * <li>The service is added to the Framework service registry and may now
+ * be used by other bundles.
+ * <li>A service event of type {@link ServiceEvent#REGISTERED} is fired.
+ * <li>A <code>ServiceRegistration</code> object for this registration is
+ * returned.
+ * </ol>
+ *
+ * @param context The BundleContext of the registering bundle.
+ * @param clazzes The class names under which the service can be located.
+ * The class names in this array will be stored in the service's
+ * properties under the key {@link Constants#OBJECTCLASS}.
+ * @param service The service object or a <code>ServiceFactory</code>
+ * object.
+ * @param properties The properties for this service. The keys in the
+ * properties object must all be <code>String</code> objects. See
+ * {@link Constants} for a list of standard service property keys.
+ * Changes should not be made to this object after calling this
+ * method. To update the service's properties the
+ * {@link ServiceRegistration#setProperties} method must be called.
+ * The set of properties may be <code>null</code> if the service
+ * has no properties.
+ *
+ * @return A <code>ServiceRegistrationImpl</code> object for use by the bundle
+ * registering the service to update the service's properties or to
+ * unregister the service.
+ *
+ * @throws java.lang.IllegalArgumentException If one of the following is
+ * true:
+ * <ul>
+ * <li><code>service</code> is <code>null</code>.
+ * <li><code>service</code> is not a <code>ServiceFactory</code>
+ * object and is not an instance of all the named classes in
+ * <code>clazzes</code>.
+ * <li><code>properties</code> contains case variants of the same
+ * key name.
+ * </ul>
+ *
+ * @throws java.lang.SecurityException If the caller does not have the
+ * <code>ServicePermission</code> to register the service for all
+ * the named classes and the Java Runtime Environment supports
+ * permissions.
+ *
+ * @throws java.lang.IllegalStateException If this BundleContext is no
+ * longer valid.
+ *
+ * @see ServiceRegistration
+ * @see ServiceFactory
+ */
+ public ServiceRegistrationImpl<?> registerService(BundleContextImpl context, String[] clazzes, Object service, Dictionary<String, ?> properties) {
+ if (service == null) {
+ if (Debug.DEBUG_SERVICES) {
+ Debug.println("Service object is null"); //$NON-NLS-1$
+ }
+
+ throw new IllegalArgumentException(Msg.SERVICE_ARGUMENT_NULL_EXCEPTION);
+ }
+
+ int size = clazzes.length;
+
+ if (size == 0) {
+ if (Debug.DEBUG_SERVICES) {
+ Debug.println("Classes array is empty"); //$NON-NLS-1$
+ }
+
+ throw new IllegalArgumentException(Msg.SERVICE_EMPTY_CLASS_LIST_EXCEPTION);
+ }
+
+ /* copy the array so that changes to the original will not affect us. */
+ List<String> copy = new ArrayList<String>(size);
+ // intern the strings and remove duplicates
+ for (int i = 0; i < size; i++) {
+ String clazz = clazzes[i].intern();
+ if (!copy.contains(clazz)) {
+ copy.add(clazz);
+ }
+ }
+ size = copy.size();
+ clazzes = copy.toArray(new String[size]);
+
+ /* check for ServicePermissions. */
+ checkRegisterServicePermission(clazzes);
+
+ if (!(service instanceof ServiceFactory<?>)) {
+ String invalidService = checkServiceClass(clazzes, service);
+ if (invalidService != null) {
+ if (Debug.DEBUG_SERVICES) {
+ Debug.println("Service object is not an instanceof " + invalidService); //$NON-NLS-1$
+ }
+ throw new IllegalArgumentException(NLS.bind(Msg.SERVICE_NOT_INSTANCEOF_CLASS_EXCEPTION, invalidService));
+ }
+ }
+
+ ServiceRegistrationImpl<?> registration = new ServiceRegistrationImpl<Object>(this, context, clazzes, service);
+ registration.register(properties);
+ if (copy.contains(listenerHookName)) {
+ notifyNewListenerHook(registration);
+ }
+ return registration;
+ }
+
+ /**
+ * Returns an array of <code>ServiceReferenceImpl</code> objects. The returned
+ * array of <code>ServiceReferenceImpl</code> objects contains services that
+ * were registered under the specified class, match the specified filter
+ * criteria, and the packages for the class names under which the services
+ * were registered match the context bundle's packages as defined in
+ * {@link ServiceReference#isAssignableTo(Bundle, String)}.
+ *
+ * <p>
+ * The list is valid at the time of the call to this method, however since
+ * the Framework is a very dynamic environment, services can be modified or
+ * unregistered at anytime.
+ *
+ * <p>
+ * <code>filter</code> is used to select the registered service whose
+ * properties objects contain keys and values which satisfy the filter. See
+ * {@link Filter} for a description of the filter string syntax.
+ *
+ * <p>
+ * If <code>filter</code> is <code>null</code>, all registered services
+ * are considered to match the filter. If <code>filter</code> cannot be
+ * parsed, an {@link InvalidSyntaxException} will be thrown with a human
+ * readable message where the filter became unparsable.
+ *
+ * <p>
+ * The following steps are required to select a set of
+ * <code>ServiceReferenceImpl</code> objects:
+ * <ol>
+ * <li>If the filter string is not <code>null</code>, the filter string
+ * is parsed and the set <code>ServiceReferenceImpl</code> objects of
+ * registered services that satisfy the filter is produced. If the filter
+ * string is <code>null</code>, then all registered services are
+ * considered to satisfy the filter.
+ * <li>If the Java Runtime Environment supports permissions, the set of
+ * <code>ServiceReferenceImpl</code> objects produced by the previous step is
+ * reduced by checking that the caller has the
+ * <code>ServicePermission</code> to get at least one of the class names
+ * under which the service was registered. If the caller does not have the
+ * correct permission for a particular <code>ServiceReferenceImpl</code>
+ * object, then it is removed from the set.
+ * <li>If <code>clazz</code> is not <code>null</code>, the set is
+ * further reduced to those services that are an <code>instanceof</code>
+ * and were registered under the specified class. The complete list of
+ * classes of which a service is an instance and which were specified when
+ * the service was registered is available from the service's
+ * {@link Constants#OBJECTCLASS} property.
+ * <li>The set is reduced one final time by cycling through each
+ * <code>ServiceReference</code> object and calling
+ * {@link ServiceReference#isAssignableTo(Bundle, String)} with the context
+ * bundle and each class name under which the <code>ServiceReference</code>
+ * object was registered. For any given <code>ServiceReferenceImpl</code>
+ * object, if any call to
+ * {@link ServiceReference#isAssignableTo(Bundle, String)} returns
+ * <code>false</code>, then it is removed from the set of
+ * <code>ServiceReferenceImpl</code> objects.
+ * <li>An array of the remaining <code>ServiceReferenceImpl</code> objects is
+ * returned.
+ * </ol>
+ *
+ * @param context The BundleContext of the requesting bundle.
+ * @param clazz The class name with which the service was registered or
+ * <code>null</code> for all services.
+ * @param filterstring The filter criteria.
+ * @param allservices True if the bundle called getAllServiceReferences.
+ * @param callHooks True if the references should be filtered using service find hooks.
+ * @return An array of <code>ServiceReferenceImpl</code> objects or
+ * <code>null</code> if no services are registered which satisfy
+ * the search.
+ * @throws InvalidSyntaxException If <code>filter</code> contains an
+ * invalid filter string that cannot be parsed.
+ * @throws java.lang.IllegalStateException If this BundleContext is no
+ * longer valid.
+ */
+ public ServiceReferenceImpl<?>[] getServiceReferences(final BundleContextImpl context, final String clazz, final String filterstring, final boolean allservices, boolean callHooks) throws InvalidSyntaxException {
+ if (Debug.DEBUG_SERVICES) {
+ Debug.println((allservices ? "getAllServiceReferences(" : "getServiceReferences(") + clazz + ", \"" + filterstring + "\")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ }
+ Filter filter = (filterstring == null) ? null : context.createFilter(filterstring);
+ List<ServiceRegistrationImpl<?>> registrations = lookupServiceRegistrations(clazz, filter);
+ List<ServiceReferenceImpl<?>> references = new ArrayList<ServiceReferenceImpl<?>>(registrations.size());
+ for (ServiceRegistrationImpl<?> registration : registrations) {
+ ServiceReferenceImpl<?> reference;
+ try {
+ reference = registration.getReferenceImpl();
+ } catch (IllegalStateException e) {
+ continue; // got unregistered, don't return reference
+ }
+ if (allservices || isAssignableTo(context, reference)) {
+ try { /* test for permission to get the service */
+ checkGetServicePermission(reference);
+ } catch (SecurityException se) {
+ continue; // don't return reference
+ }
+ } else {
+ continue; // don't return reference
+ }
+ references.add(reference);
+ }
+
+ if (callHooks) {
+ Collection<ServiceReference<?>> shrinkable = new ShrinkableCollection<ServiceReference<?>>(references);
+ notifyFindHooks(context, clazz, filterstring, allservices, shrinkable);
+ }
+ int size = references.size();
+ if (size == 0) {
+ return null;
+ }
+ return references.toArray(new ServiceReferenceImpl[size]);
+ }
+
+ /**
+ * This method performs the same function as calling
+ * {@link #getServiceReferences(BundleContextImpl, String, String, boolean, boolean)} with a
+ * {@code true} callHooks value.
+ * @param context The BundleContext of the requesting bundle.
+ * @param clazz The class name with which the service was registered or
+ * <code>null</code> for all services.
+ * @param filterstring The filter criteria.
+ * @param allservices True if the bundle called getAllServiceReferences.
+ * @return An array of <code>ServiceReferenceImpl</code> objects or
+ * <code>null</code> if no services are registered which satisfy
+ * the search.
+ * @throws InvalidSyntaxException If <code>filter</code> contains an
+ * invalid filter string that cannot be parsed.
+ * @throws java.lang.IllegalStateException If this BundleContext is no
+ * longer valid.
+ */
+ public ServiceReferenceImpl<?>[] getServiceReferences(final BundleContextImpl context, final String clazz, final String filterstring, final boolean allservices) throws InvalidSyntaxException {
+ return getServiceReferences(context, clazz, filterstring, allservices, true);
+ }
+
+ /**
+ * Returns a <code>ServiceReference</code> object for a service that
+ * implements and was registered under the specified class.
+ *
+ * <p>
+ * This <code>ServiceReference</code> object is valid at the time of the
+ * call to this method, however as the Framework is a very dynamic
+ * environment, services can be modified or unregistered at anytime.
+ *
+ * <p>
+ * This method is the same as calling
+ * {@link BundleContext#getServiceReferences(String, String)} with a
+ * <code>null</code> filter string. It is provided as a convenience for
+ * when the caller is interested in any service that implements the
+ * specified class.
+ * <p>
+ * If multiple such services exist, the service with the highest ranking (as
+ * specified in its {@link Constants#SERVICE_RANKING} property) is returned.
+ * <p>
+ * If there is a tie in ranking, the service with the lowest service ID (as
+ * specified in its {@link Constants#SERVICE_ID} property); that is, the
+ * service that was registered first is returned.
+ *
+ * @param context The BundleContext of the requesting bundle.
+ * @param clazz The class name with which the service was registered.
+ * @return A <code>ServiceReference</code> object, or <code>null</code>
+ * if no services are registered which implement the named class.
+ * @throws java.lang.IllegalStateException If this BundleContext is no
+ * longer valid.
+ */
+ public ServiceReferenceImpl<?> getServiceReference(BundleContextImpl context, String clazz) {
+ if (Debug.DEBUG_SERVICES) {
+ Debug.println("getServiceReference(" + clazz + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ try {
+ ServiceReferenceImpl<?>[] references = getServiceReferences(context, clazz, null, false);
+
+ if (references != null) {
+ // Since we maintain the registrations in a sorted List, the first element is always the
+ // correct one to return.
+ return references[0];
+ }
+ } catch (InvalidSyntaxException e) {
+ if (Debug.DEBUG_GENERAL) {
+ Debug.println("InvalidSyntaxException w/ null filter" + e.getMessage()); //$NON-NLS-1$
+ Debug.printStackTrace(e);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the specified service object for a service.
+ * <p>
+ * A bundle's use of a service is tracked by the bundle's use count of that
+ * service. Each time a service's service object is returned by
+ * {@link #getService(BundleContextImpl, ServiceReferenceImpl)} the context bundle's use count for
+ * that service is incremented by one. Each time the service is released by
+ * {@link #ungetService(BundleContextImpl, ServiceReferenceImpl)} the context bundle's use count
+ * for that service is decremented by one.
+ * <p>
+ * When a bundle's use count for a service drops to zero, the bundle should
+ * no longer use that service.
+ *
+ * <p>
+ * This method will always return <code>null</code> when the service
+ * associated with this <code>reference</code> has been unregistered.
+ *
+ * <p>
+ * The following steps are required to get the service object:
+ * <ol>
+ * <li>If the service has been unregistered, <code>null</code> is
+ * returned.
+ * <li>The context bundle's use count for this service is incremented by
+ * one.
+ * <li>If the context bundle's use count for the service is currently one
+ * and the service was registered with an object implementing the
+ * <code>ServiceFactory</code> interface, the
+ * {@link ServiceFactory#getService(Bundle, ServiceRegistration)} method is
+ * called to create a service object for the context bundle. This service
+ * object is cached by the Framework. While the context bundle's use count
+ * for the service is greater than zero, subsequent calls to get the
+ * services's service object for the context bundle will return the cached
+ * service object. <br>
+ * If the service object returned by the <code>ServiceFactory</code>
+ * object is not an <code>instanceof</code> all the classes named when the
+ * service was registered or the <code>ServiceFactory</code> object throws
+ * an exception, <code>null</code> is returned and a Framework event of
+ * type {@link FrameworkEvent#ERROR} containing a {@link ServiceException}
+ * describing the error is fired.
+ * <li>The service object for the service is returned.
+ * </ol>
+ *
+ * @param context The BundleContext of the requesting bundle.
+ * @param reference A reference to the service.
+ * @return A service object for the service associated with
+ * <code>reference</code> or <code>null</code> if the service is
+ * not registered, the service object returned by a
+ * <code>ServiceFactory</code> does not implement the classes
+ * under which it was registered or the <code>ServiceFactory</code>
+ * threw an exception.
+ * @throws java.lang.SecurityException If the caller does not have the
+ * <code>ServicePermission</code> to get the service using at
+ * least one of the named classes the service was registered under
+ * and the Java Runtime Environment supports permissions.
+ * @throws java.lang.IllegalStateException If this BundleContext is no
+ * longer valid.
+ * @see #ungetService(BundleContextImpl, ServiceReferenceImpl)
+ * @see ServiceFactory
+ */
+ public Object getService(BundleContextImpl context, ServiceReferenceImpl<?> reference) {
+ /* test for permission to get the service */
+ checkGetServicePermission(reference);
+ return reference.getRegistration().getService(context);
+ }
+
+ /**
+ * Releases the service object referenced by the specified
+ * <code>ServiceReference</code> object. If the context bundle's use count
+ * for the service is zero, this method returns <code>false</code>.
+ * Otherwise, the context bundle's use count for the service is decremented
+ * by one.
+ *
+ * <p>
+ * The service's service object should no longer be used and all references
+ * to it should be destroyed when a bundle's use count for the service drops
+ * to zero.
+ *
+ * <p>
+ * The following steps are required to unget the service object:
+ * <ol>
+ * <li>If the context bundle's use count for the service is zero or the
+ * service has been unregistered, <code>false</code> is returned.
+ * <li>The context bundle's use count for this service is decremented by
+ * one.
+ * <li>If the context bundle's use count for the service is currently zero
+ * and the service was registered with a <code>ServiceFactory</code>
+ * object, the
+ * {@link ServiceFactory#ungetService(Bundle, ServiceRegistration, Object)}
+ * method is called to release the service object for the context bundle.
+ * <li><code>true</code> is returned.
+ * </ol>
+ *
+ * @param context The BundleContext of the requesting bundle.
+ * @param reference A reference to the service to be released.
+ * @return <code>false</code> if the context bundle's use count for the
+ * service is zero or if the service has been unregistered;
+ * <code>true</code> otherwise.
+ * @throws java.lang.IllegalStateException If this BundleContext is no
+ * longer valid.
+ * @see #getService
+ * @see ServiceFactory
+ */
+ public boolean ungetService(BundleContextImpl context, ServiceReferenceImpl<?> reference) {
+ ServiceRegistrationImpl<?> registration = reference.getRegistration();
+
+ return registration.ungetService(context);
+ }
+
+ /**
+ * Returns this bundle's <code>ServiceReference</code> list for all
+ * services it has registered or <code>null</code> if this bundle has no
+ * registered services.
+ *
+ * <p>
+ * If the Java runtime supports permissions, a <code>ServiceReference</code>
+ * object to a service is included in the returned list only if the caller
+ * has the <code>ServicePermission</code> to get the service using at
+ * least one of the named classes the service was registered under.
+ *
+ * <p>
+ * The list is valid at the time of the call to this method, however, as the
+ * Framework is a very dynamic environment, services can be modified or
+ * unregistered at anytime.
+ *
+ * @param context The BundleContext of the requesting bundle.
+ * @return An array of <code>ServiceReference</code> objects or
+ * <code>null</code>.
+ * @throws java.lang.IllegalStateException If this bundle has been
+ * uninstalled.
+ * @see ServiceRegistration
+ * @see ServiceReference
+ * @see ServicePermission
+ */
+ public ServiceReferenceImpl<?>[] getRegisteredServices(BundleContextImpl context) {
+ List<ServiceRegistrationImpl<?>> registrations = lookupServiceRegistrations(context);
+ List<ServiceReferenceImpl<?>> references = new ArrayList<ServiceReferenceImpl<?>>(registrations.size());
+ for (ServiceRegistrationImpl<?> registration : registrations) {
+ ServiceReferenceImpl<?> reference;
+ try {
+ reference = registration.getReferenceImpl();
+ } catch (IllegalStateException e) {
+ continue; // got unregistered, don't return reference
+ }
+ try {
+ /* test for permission to get the service */
+ checkGetServicePermission(reference);
+ } catch (SecurityException se) {
+ continue; // don't return reference
+ }
+ references.add(reference);
+ }
+
+ int size = references.size();
+ if (size == 0) {
+ return null;
+ }
+ return references.toArray(new ServiceReferenceImpl[size]);
+ }
+
+ /**
+ * Returns this bundle's <code>ServiceReference</code> list for all
+ * services it is using or returns <code>null</code> if this bundle is not
+ * using any services. A bundle is considered to be using a service if its
+ * use count for that service is greater than zero.
+ *
+ * <p>
+ * If the Java Runtime Environment supports permissions, a
+ * <code>ServiceReference</code> object to a service is included in the
+ * returned list only if the caller has the <code>ServicePermission</code>
+ * to get the service using at least one of the named classes the service
+ * was registered under.
+ * <p>
+ * The list is valid at the time of the call to this method, however, as the
+ * Framework is a very dynamic environment, services can be modified or
+ * unregistered at anytime.
+ *
+ * @param context The BundleContext of the requesting bundle.
+ * @return An array of <code>ServiceReference</code> objects or
+ * <code>null</code>.
+ * @throws java.lang.IllegalStateException If this bundle has been
+ * uninstalled.
+ * @see ServiceReference
+ * @see ServicePermission
+ */
+ public ServiceReferenceImpl<?>[] getServicesInUse(BundleContextImpl context) {
+ Map<ServiceRegistrationImpl<?>, ServiceUse<?>> servicesInUse = context.getServicesInUseMap();
+ if (servicesInUse == null) {
+ return null;
+ }
+
+ List<ServiceRegistrationImpl<?>> registrations;
+ synchronized (servicesInUse) {
+ if (servicesInUse.isEmpty()) {
+ return null;
+ }
+ registrations = new ArrayList<ServiceRegistrationImpl<?>>(servicesInUse.keySet());
+ }
+ List<ServiceReferenceImpl<?>> references = new ArrayList<ServiceReferenceImpl<?>>(registrations.size());
+ for (ServiceRegistrationImpl<?> registration : registrations) {
+ ServiceReferenceImpl<?> reference;
+ try {
+ reference = registration.getReferenceImpl();
+ } catch (IllegalStateException e) {
+ continue; // got unregistered, don't return reference
+ }
+ try {
+ /* test for permission to get the service */
+ checkGetServicePermission(reference);
+ } catch (SecurityException se) {
+ continue; // don't return reference
+ }
+ references.add(reference);
+ }
+
+ int size = references.size();
+ if (size == 0) {
+ return null;
+ }
+ return references.toArray(new ServiceReferenceImpl[size]);
+ }
+
+ /**
+ * Called when the BundleContext is closing to unregister all services
+ * currently registered by the bundle.
+ *
+ * @param context The BundleContext of the closing bundle.
+ */
+ public void unregisterServices(BundleContextImpl context) {
+ for (ServiceRegistrationImpl<?> registration : lookupServiceRegistrations(context)) {
+ try {
+ registration.unregister();
+ } catch (IllegalStateException e) {
+ /* already unregistered */
+ }
+ }
+ removeServiceRegistrations(context); // remove empty list
+ }
+
+ /**
+ * Called when the BundleContext is closing to unget all services
+ * currently used by the bundle.
+ *
+ * @param context The BundleContext of the closing bundle.
+ */
+ public void releaseServicesInUse(BundleContextImpl context) {
+ Map<ServiceRegistrationImpl<?>, ServiceUse<?>> servicesInUse = context.getServicesInUseMap();
+ if (servicesInUse == null) {
+ return;
+ }
+ List<ServiceRegistrationImpl<?>> registrations;
+ synchronized (servicesInUse) {
+ if (servicesInUse.isEmpty()) {
+ return;
+ }
+ registrations = new ArrayList<ServiceRegistrationImpl<?>>(servicesInUse.keySet());
+ }
+ if (Debug.DEBUG_SERVICES) {
+ Debug.println("Releasing services"); //$NON-NLS-1$
+ }
+ for (ServiceRegistrationImpl<?> registration : registrations) {
+ registration.releaseService(context);
+ }
+ }
+
+ /**
+ * Add a new Service Listener for a bundle.
+ *
+ * @param context Context of bundle adding listener.
+ * @param listener Service Listener to be added.
+ * @param filter Filter string for listener or null.
+ * @throws InvalidSyntaxException If the filter string is invalid.
+ */
+ public void addServiceListener(BundleContextImpl context, ServiceListener listener, String filter) throws InvalidSyntaxException {
+ if (Debug.DEBUG_EVENTS) {
+ String listenerName = listener.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(listener)); //$NON-NLS-1$
+ Debug.println("addServiceListener[" + context.getBundleImpl() + "](" + listenerName + ", \"" + filter + "\")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ }
+
+ FilteredServiceListener filteredListener = new FilteredServiceListener(context, listener, filter);
+ FilteredServiceListener oldFilteredListener;
+ synchronized (serviceEventListeners) {
+ CopyOnWriteIdentityMap<ServiceListener, FilteredServiceListener> listeners = serviceEventListeners.get(context);
+ if (listeners == null) {
+ listeners = new CopyOnWriteIdentityMap<ServiceListener, FilteredServiceListener>();
+ serviceEventListeners.put(context, listeners);
+ }
+ oldFilteredListener = listeners.put(listener, filteredListener);
+ }
+
+ if (oldFilteredListener != null) {
+ oldFilteredListener.markRemoved();
+ Collection<ListenerInfo> removedListeners = Collections.<ListenerInfo> singletonList(oldFilteredListener);
+ notifyListenerHooks(removedListeners, false);
+ }
+
+ Collection<ListenerInfo> addedListeners = Collections.<ListenerInfo> singletonList(filteredListener);
+ notifyListenerHooks(addedListeners, true);
+ }
+
+ /**
+ * Remove a Service Listener for a bundle.
+ *
+ * @param context Context of bundle removing listener.
+ * @param listener Service Listener to be removed.
+ */
+ public void removeServiceListener(BundleContextImpl context, ServiceListener listener) {
+ if (Debug.DEBUG_EVENTS) {
+ String listenerName = listener.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(listener)); //$NON-NLS-1$
+ Debug.println("removeServiceListener[" + context.getBundleImpl() + "](" + listenerName + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ FilteredServiceListener oldFilteredListener;
+ synchronized (serviceEventListeners) {
+ Map<ServiceListener, FilteredServiceListener> listeners = serviceEventListeners.get(context);
+ if (listeners == null) {
+ return; // this context has no listeners to begin with
+ }
+ oldFilteredListener = listeners.remove(listener);
+ }
+
+ if (oldFilteredListener == null) {
+ return;
+ }
+ oldFilteredListener.markRemoved();
+ Collection<ListenerInfo> removedListeners = Collections.<ListenerInfo> singletonList(oldFilteredListener);
+ notifyListenerHooks(removedListeners, false);
+ }
+
+ /**
+ * Remove all Service Listener for a bundle.
+ *
+ * @param context Context of bundle removing all listeners.
+ */
+ public void removeAllServiceListeners(BundleContextImpl context) {
+ Map<ServiceListener, FilteredServiceListener> removedListenersMap;
+ synchronized (serviceEventListeners) {
+ removedListenersMap = serviceEventListeners.remove(context);
+ }
+ if ((removedListenersMap == null) || removedListenersMap.isEmpty()) {
+ return;
+ }
+ Collection<FilteredServiceListener> removedListeners = removedListenersMap.values();
+ for (FilteredServiceListener oldFilteredListener : removedListeners) {
+ oldFilteredListener.markRemoved();
+ }
+ notifyListenerHooks(asListenerInfos(removedListeners), false);
+ }
+
+ /**
+ * Coerce the generic type of a collection from Collection<FilteredServiceListener>
+ * to Collection<ListenerInfo>
+ * @param c Collection to be coerced.
+ * @return c coerced to Collection<ListenerInfo>
+ */
+ @SuppressWarnings("unchecked")
+ private static Collection<ListenerInfo> asListenerInfos(Collection<? extends ListenerInfo> c) {
+ return (Collection<ListenerInfo>) c;
+ }
+
+ /**
+ * Deliver a ServiceEvent.
+ *
+ * @param event The ServiceEvent to deliver.
+ */
+ public void publishServiceEvent(final ServiceEvent event) {
+ if (System.getSecurityManager() == null) {
+ publishServiceEventPrivileged(event);
+ } else {
+ AccessController.doPrivileged(new PrivilegedAction<Object>() {
+ public Object run() {
+ publishServiceEventPrivileged(event);
+ return null;
+ }
+ });
+ }
+ }
+
+ void publishServiceEventPrivileged(final ServiceEvent event) {
+ /* Build the listener snapshot */
+ Map<BundleContextImpl, Set<Map.Entry<ServiceListener, FilteredServiceListener>>> listenerSnapshot;
+ synchronized (serviceEventListeners) {
+ listenerSnapshot = new HashMap<BundleContextImpl, Set<Map.Entry<ServiceListener, FilteredServiceListener>>>(serviceEventListeners.size());
+ for (Map.Entry<BundleContextImpl, CopyOnWriteIdentityMap<ServiceListener, FilteredServiceListener>> entry : serviceEventListeners.entrySet()) {
+ CopyOnWriteIdentityMap<ServiceListener, FilteredServiceListener> listeners = entry.getValue();
+ if (!listeners.isEmpty()) {
+ listenerSnapshot.put(entry.getKey(), listeners.entrySet());
+ }
+ }
+ }
+
+ /* shrink the snapshot.
+ * keySet returns a Collection which cannot be added to and
+ * removals from that collection will result in removals of the
+ * entry from the snapshot.
+ */
+ Collection<BundleContext> contexts = asBundleContexts(listenerSnapshot.keySet());
+ notifyEventHooksPrivileged(event, contexts);
+ if (listenerSnapshot.isEmpty()) {
+ return;
+ }
+ Map<BundleContext, Collection<ListenerInfo>> listeners = new ShrinkableValueCollectionMap<BundleContext, ListenerInfo>(listenerSnapshot);
+ notifyEventListenerHooksPrivileged(event, listeners);
+ if (listenerSnapshot.isEmpty()) {
+ return;
+ }
+
+ /* deliver the event to the snapshot */
+ ListenerQueue<ServiceListener, FilteredServiceListener, ServiceEvent> queue = framework.newListenerQueue();
+ for (Map.Entry<BundleContextImpl, Set<Map.Entry<ServiceListener, FilteredServiceListener>>> entry : listenerSnapshot.entrySet()) {
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ EventDispatcher<ServiceListener, FilteredServiceListener, ServiceEvent> dispatcher = (EventDispatcher) entry.getKey();
+ Set<Map.Entry<ServiceListener, FilteredServiceListener>> listenerSet = entry.getValue();
+ queue.queueListeners(listenerSet, dispatcher);
+ }
+ queue.dispatchEventSynchronous(SERVICEEVENT, event);
+ }
+
+ /**
+ * Coerce the generic type of a collection from Collection<BundleContextImpl>
+ * to Collection<BundleContext>
+ * @param c Collection to be coerced.
+ * @return c coerced to Collection<BundleContext>
+ */
+ @SuppressWarnings("unchecked")
+ private static Collection<BundleContext> asBundleContexts(Collection<? extends BundleContext> c) {
+ return (Collection<BundleContext>) c;
+ }
+
+ /**
+ * Return the next available service id.
+ *
+ * @return next service id.
+ */
+ synchronized long getNextServiceId() {
+ long id = serviceid;
+ serviceid = id + 1;
+ return id;
+ }
+
+ /**
+ * Add the ServiceRegistrationImpl to the data structure.
+ *
+ * @param context The BundleContext of the bundle registering the service.
+ * @param registration The new ServiceRegistration.
+ */
+ /* @GuardedBy("this") */
+ void addServiceRegistration(BundleContextImpl context, ServiceRegistrationImpl<?> registration) {
+ assert Thread.holdsLock(this);
+ // Add the ServiceRegistrationImpl to the list of Services published by BundleContextImpl.
+ List<ServiceRegistrationImpl<?>> contextServices = publishedServicesByContext.get(context);
+ if (contextServices == null) {
+ contextServices = new ArrayList<ServiceRegistrationImpl<?>>(initialSubCapacity);
+ publishedServicesByContext.put(context, contextServices);
+ }
+ // The list is NOT sorted, so we just add
+ contextServices.add(registration);
+
+ // Add the ServiceRegistrationImpl to the list of Services published by Class Name.
+ int insertIndex;
+ for (String clazz : registration.getClasses()) {
+ List<ServiceRegistrationImpl<?>> services = publishedServicesByClass.get(clazz);
+
+ if (services == null) {
+ services = new ArrayList<ServiceRegistrationImpl<?>>(initialSubCapacity);
+ publishedServicesByClass.put(clazz, services);
+ }
+
+ // The list is sorted, so we must find the proper location to insert
+ insertIndex = -Collections.binarySearch(services, registration) - 1;
+ services.add(insertIndex, registration);
+ }
+
+ // Add the ServiceRegistrationImpl to the list of all published Services.
+ // The list is sorted, so we must find the proper location to insert
+ insertIndex = -Collections.binarySearch(allPublishedServices, registration) - 1;
+ allPublishedServices.add(insertIndex, registration);
+ }
+
+ /**
+ * Modify the ServiceRegistrationImpl in the data structure.
+ *
+ * @param context The BundleContext of the bundle registering the service.
+ * @param registration The modified ServiceRegistration.
+ */
+ /* @GuardedBy("this") */
+ void modifyServiceRegistration(BundleContextImpl context, ServiceRegistrationImpl<?> registration) {
+ assert Thread.holdsLock(this);
+ // The list of Services published by BundleContextImpl is not sorted, so
+ // we do not need to modify it.
+
+ // Remove the ServiceRegistrationImpl from the list of Services published by Class Name
+ // and then add at the correct index.
+ int insertIndex;
+ for (String clazz : registration.getClasses()) {
+ List<ServiceRegistrationImpl<?>> services = publishedServicesByClass.get(clazz);
+ services.remove(registration);
+ // The list is sorted, so we must find the proper location to insert
+ insertIndex = -Collections.binarySearch(services, registration) - 1;
+ services.add(insertIndex, registration);
+ }
+
+ // Remove the ServiceRegistrationImpl from the list of all published Services
+ // and then add at the correct index.
+ allPublishedServices.remove(registration);
+ // The list is sorted, so we must find the proper location to insert
+ insertIndex = -Collections.binarySearch(allPublishedServices, registration) - 1;
+ allPublishedServices.add(insertIndex, registration);
+ }
+
+ /**
+ * Remove the ServiceRegistrationImpl from the data structure.
+ *
+ * @param context The BundleContext of the bundle registering the service.
+ * @param registration The ServiceRegistration to remove.
+ */
+ /* @GuardedBy("this") */
+ void removeServiceRegistration(BundleContextImpl context, ServiceRegistrationImpl<?> registration) {
+ assert Thread.holdsLock(this);
+ // Remove the ServiceRegistrationImpl from the list of Services published by BundleContextImpl.
+ List<ServiceRegistrationImpl<?>> contextServices = publishedServicesByContext.get(context);
+ if (contextServices != null) {
+ contextServices.remove(registration);
+ }
+
+ // Remove the ServiceRegistrationImpl from the list of Services published by Class Name.
+ for (String clazz : registration.getClasses()) {
+ List<ServiceRegistrationImpl<?>> services = publishedServicesByClass.get(clazz);
+ services.remove(registration);
+ if (services.isEmpty()) { // remove empty list
+ publishedServicesByClass.remove(clazz);
+ }
+ }
+
+ // Remove the ServiceRegistrationImpl from the list of all published Services.
+ allPublishedServices.remove(registration);
+ }
+
+ /**
+ * Lookup Service Registrations in the data structure by class name and filter.
+ *
+ * @param clazz The class name with which the service was registered or
+ * <code>null</code> for all services.
+ * @param filter The filter criteria.
+ * @return List<ServiceRegistrationImpl>
+ */
+ private List<ServiceRegistrationImpl<?>> lookupServiceRegistrations(String clazz, Filter filter) {
+ List<ServiceRegistrationImpl<?>> result;
+ synchronized (this) {
+ if (clazz == null) { /* all services */
+ result = allPublishedServices;
+ } else {
+ /* services registered under the class name */
+ result = publishedServicesByClass.get(clazz);
+ }
+
+ if ((result == null) || result.isEmpty()) {
+ @SuppressWarnings("unchecked")
+ List<ServiceRegistrationImpl<?>> empty = Collections.EMPTY_LIST;
+ return empty;
+ }
+
+ result = new LinkedList<ServiceRegistrationImpl<?>>(result); /* make a new list since we don't want to change the real list */
+ }
+
+ if (filter == null) {
+ return result;
+ }
+
+ for (Iterator<ServiceRegistrationImpl<?>> iter = result.iterator(); iter.hasNext();) {
+ ServiceRegistrationImpl<?> registration = iter.next();
+ ServiceReferenceImpl<?> reference;
+ try {
+ reference = registration.getReferenceImpl();
+ } catch (IllegalStateException e) {
+ iter.remove(); /* service was unregistered after we left the synchronized block above */
+ continue;
+ }
+ if (!filter.match(reference)) {
+ iter.remove();
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Lookup Service Registrations in the data structure by BundleContext.
+ *
+ * @param context The BundleContext for which to return Service Registrations.
+ * @return List<ServiceRegistrationImpl>
+ */
+ private synchronized List<ServiceRegistrationImpl<?>> lookupServiceRegistrations(BundleContextImpl context) {
+ List<ServiceRegistrationImpl<?>> result = publishedServicesByContext.get(context);
+
+ if ((result == null) || result.isEmpty()) {
+ @SuppressWarnings("unchecked")
+ List<ServiceRegistrationImpl<?>> empty = Collections.EMPTY_LIST;
+ return empty;
+ }
+
+ return new ArrayList<ServiceRegistrationImpl<?>>(result); /* make a new list since we don't want to change the real list */
+ }
+
+ /**
+ * Remove Service Registrations in the data structure by BundleContext.
+ *
+ * @param context The BundleContext for which to remove Service Registrations.
+ */
+ private synchronized void removeServiceRegistrations(BundleContextImpl context) {
+ publishedServicesByContext.remove(context);
+ }
+
+ /**
+ * Check for permission to register a service.
+ *
+ * The caller must have permission for ALL names.
+ */
+ private static void checkRegisterServicePermission(String[] names) {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm == null) {
+ return;
+ }
+ for (int i = 0, len = names.length; i < len; i++) {
+ sm.checkPermission(new ServicePermission(names[i], ServicePermission.REGISTER));
+ }
+ }
+
+ /**
+ * Check for permission to get a service.
+ */
+ private static void checkGetServicePermission(ServiceReference<?> reference) {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm == null) {
+ return;
+ }
+ sm.checkPermission(new ServicePermission(reference, ServicePermission.GET));
+ }
+
+ /**
+ * Check for permission to listen to a service.
+ */
+ static boolean hasListenServicePermission(ServiceEvent event, BundleContextImpl context) {
+ ProtectionDomain domain = context.getBundleImpl().getProtectionDomain();
+ if (domain == null) {
+ return true;
+ }
+
+ return domain.implies(new ServicePermission(event.getServiceReference(), ServicePermission.GET));
+ }
+
+ /**
+ * Return the name of the class that is not satisfied by the service object.
+ * @param clazzes Array of class names.
+ * @param serviceObject Service object.
+ * @return The name of the class that is not satisfied by the service object.
+ */
+ static String checkServiceClass(final String[] clazzes, final Object serviceObject) {
+ ClassLoader cl = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
+ public ClassLoader run() {
+ return serviceObject.getClass().getClassLoader();
+ }
+ });
+ for (int i = 0, len = clazzes.length; i < len; i++) {
+ try {
+ Class<?> serviceClazz = cl == null ? Class.forName(clazzes[i]) : cl.loadClass(clazzes[i]);
+ if (!serviceClazz.isInstance(serviceObject))
+ return clazzes[i];
+ } catch (ClassNotFoundException e) {
+ //This check is rarely done
+ if (extensiveCheckServiceClass(clazzes[i], serviceObject.getClass()))
+ return clazzes[i];
+ }
+ }
+ return null;
+ }
+
+ private static boolean extensiveCheckServiceClass(String clazz, Class<?> serviceClazz) {
+ if (clazz.equals(serviceClazz.getName()))
+ return false;
+ Class<?>[] interfaces = serviceClazz.getInterfaces();
+ for (int i = 0, len = interfaces.length; i < len; i++)
+ if (!extensiveCheckServiceClass(clazz, interfaces[i]))
+ return false;
+ Class<?> superClazz = serviceClazz.getSuperclass();
+ if (superClazz != null)
+ if (!extensiveCheckServiceClass(clazz, superClazz))
+ return false;
+ return true;
+ }
+
+ static boolean isAssignableTo(BundleContextImpl context, ServiceReferenceImpl<?> reference) {
+ Bundle bundle = context.getBundleImpl();
+ String[] clazzes = reference.getClasses();
+ for (int i = 0, len = clazzes.length; i < len; i++)
+ if (!reference.isAssignableTo(bundle, clazzes[i]))
+ return false;
+ return true;
+ }
+
+ /**
+ * Call the registered FindHook services to allow them to inspect and possibly shrink the result.
+ * The FindHook must be called in order: descending by service.ranking, then ascending by service.id.
+ * This is the natural order for ServiceReference.
+ *
+ * @param context The context of the bundle getting the service references.
+ * @param clazz The class name used to search for the service references.
+ * @param filterstring The filter used to search for the service references.
+ * @param allservices True if getAllServiceReferences called.
+ * @param result The result to return to the caller which may have been shrunk by the FindHooks.
+ */
+ private void notifyFindHooks(final BundleContextImpl context, final String clazz, final String filterstring, final boolean allservices, final Collection<ServiceReference<?>> result) {
+ if (System.getSecurityManager() == null) {
+ notifyFindHooksPrivileged(context, clazz, filterstring, allservices, result);
+ } else {
+ AccessController.doPrivileged(new PrivilegedAction<Object>() {
+ public Object run() {
+ notifyFindHooksPrivileged(context, clazz, filterstring, allservices, result);
+ return null;
+ }
+ });
+ }
+ }
+
+ void notifyFindHooksPrivileged(final BundleContextImpl context, final String clazz, final String filterstring, final boolean allservices, final Collection<ServiceReference<?>> result) {
+ if (Debug.DEBUG_HOOKS) {
+ Debug.println("notifyServiceFindHooks(" + context.getBundleImpl() + "," + clazz + "," + filterstring + "," + allservices + "," + result + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
+ }
+ notifyHooksPrivileged(new HookContext() {
+ public void call(Object hook, ServiceRegistration<?> hookRegistration) throws Exception {
+ if (hook instanceof FindHook) {
+ ((FindHook) hook).find(context, clazz, filterstring, allservices, result);
+ }
+ }
+
+ public String getHookClassName() {
+ return findHookName;
+ }
+
+ public String getHookMethodName() {
+ return "find"; //$NON-NLS-1$
+ }
+ });
+ }
+
+ /**
+ * Call the registered EventHook services to allow them to inspect and possibly shrink the result.
+ * The EventHooks must be called in order: descending by service.ranking, then ascending by service.id.
+ * This is the natural order for ServiceReference.
+ *
+ * @param event The service event to be delivered.
+ * @param result The result to return to the caller which may have been shrunk by the EventHooks.
+ */
+ private void notifyEventHooksPrivileged(final ServiceEvent event, final Collection<BundleContext> result) {
+ if (Debug.DEBUG_HOOKS) {
+ Debug.println("notifyServiceEventHooks(" + event.getType() + ":" + event.getServiceReference() + "," + result + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ }
+ notifyHooksPrivileged(new HookContext() {
+ public void call(Object hook, ServiceRegistration<?> hookRegistration) throws Exception {
+ if (hook instanceof EventHook) {
+ ((EventHook) hook).event(event, result);
+ }
+ }
+
+ public String getHookClassName() {
+ return eventHookName;
+ }
+
+ public String getHookMethodName() {
+ return "event"; //$NON-NLS-1$
+ }
+ });
+ }
+
+ /**
+ * Call the registered EventListenerHook services to allow them to inspect and possibly shrink the result.
+ * The EventListenerHooks must be called in order: descending by service.ranking, then ascending by service.id.
+ * This is the natural order for ServiceReference.
+ *
+ * @param event The service event to be delivered.
+ * @param result The result to return to the caller which may have been shrunk by the EventListenerHooks.
+ */
+ private void notifyEventListenerHooksPrivileged(final ServiceEvent event, final Map<BundleContext, Collection<ListenerInfo>> result) {
+ if (Debug.DEBUG_HOOKS) {
+ Debug.println("notifyServiceEventListenerHooks(" + event.getType() + ":" + event.getServiceReference() + "," + result + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ }
+ notifyHooksPrivileged(new HookContext() {
+ public void call(Object hook, ServiceRegistration<?> hookRegistration) throws Exception {
+ if (hook instanceof EventListenerHook) {
+ ((EventListenerHook) hook).event(event, result);
+ }
+ }
+
+ public String getHookClassName() {
+ return eventListenerHookName;
+ }
+
+ public String getHookMethodName() {
+ return "event"; //$NON-NLS-1$
+ }
+ });
+ }
+
+ /**
+ * Calls all hook services of the type specified by the hook context.
+ *
+ * @param hookContext Context to use when calling the hook services.
+ */
+ public void notifyHooksPrivileged(HookContext hookContext) {
+ BundleContextImpl systemBundleContext = framework.getSystemBundleContext();
+ if (systemBundleContext == null) { // if no system bundle context, we are done!
+ return;
+ }
+
+ List<ServiceRegistrationImpl<?>> hooks = lookupServiceRegistrations(hookContext.getHookClassName(), null);
+ // Since the list is already sorted, we don't need to sort the list to call the hooks
+ // in the proper order.
+
+ for (ServiceRegistrationImpl<?> registration : hooks) {
+ notifyHookPrivileged(systemBundleContext, registration, hookContext);
+ }
+ }
+
+ /**
+ * Call a hook service via a hook context.
+ *
+ * @param context Context of the bundle to get the hook service.
+ * @param registration Hook service to call.
+ * @param hookContext Context to use when calling the hook service.
+ */
+ private void notifyHookPrivileged(BundleContextImpl context, ServiceRegistrationImpl<?> registration, HookContext hookContext) {
+ Object hook = registration.getSafeService(context);
+ if (hook == null) { // if the hook is null
+ return;
+ }
+ try {
+ hookContext.call(hook, registration);
+ } catch (Throwable t) {
+ if (Debug.DEBUG_HOOKS) {
+ Debug.println(hook.getClass().getName() + "." + hookContext.getHookMethodName() + "() exception: " + t.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$
+ Debug.printStackTrace(t);
+ }
+ // allow the adaptor to handle this unexpected error
+ framework.getAdaptor().handleRuntimeError(t);
+ ServiceException se = new ServiceException(NLS.bind(Msg.SERVICE_FACTORY_EXCEPTION, hook.getClass().getName(), hookContext.getHookMethodName()), t);
+ framework.publishFrameworkEvent(FrameworkEvent.ERROR, registration.getBundle(), se);
+ } finally {
+ registration.ungetService(context);
+ }
+ }
+
+ /**
+ * Call a newly registered ListenerHook service to provide the current collection of
+ * service listeners.
+ *
+ * @param registration The newly registered ListenerHook service.
+ */
+ private void notifyNewListenerHook(final ServiceRegistrationImpl<?> registration) {
+ if (System.getSecurityManager() == null) {
+ notifyNewListenerHookPrivileged(registration);
+ } else {
+ AccessController.doPrivileged(new PrivilegedAction<Object>() {
+ public Object run() {
+ notifyNewListenerHookPrivileged(registration);
+ return null;
+ }
+ });
+ }
+
+ }
+
+ void notifyNewListenerHookPrivileged(ServiceRegistrationImpl<?> registration) {
+ BundleContextImpl systemBundleContext = framework.getSystemBundleContext();
+ if (systemBundleContext == null) { // if no system bundle context, we are done!
+ return;
+ }
+
+ if (Debug.DEBUG_HOOKS) {
+ Debug.println("notifyServiceNewListenerHook(" + registration + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ // snapshot the listeners
+ Collection<ListenerInfo> addedListeners = new ArrayList<ListenerInfo>(initialCapacity);
+ synchronized (serviceEventListeners) {
+ for (CopyOnWriteIdentityMap<ServiceListener, FilteredServiceListener> listeners : serviceEventListeners.values()) {
+ if (!listeners.isEmpty()) {
+ addedListeners.addAll(listeners.values());
+ }
+ }
+ }
+
+ final Collection<ListenerInfo> listeners = Collections.unmodifiableCollection(addedListeners);
+ notifyHookPrivileged(systemBundleContext, registration, new HookContext() {
+ public void call(Object hook, ServiceRegistration<?> hookRegistration) throws Exception {
+ if (hook instanceof ListenerHook) {
+ ((ListenerHook) hook).added(listeners);
+ }
+ }
+
+ public String getHookClassName() {
+ return listenerHookName;
+ }
+
+ public String getHookMethodName() {
+ return "added"; //$NON-NLS-1$
+ }
+ });
+ }
+
+ /**
+ * Call the registered ListenerHook services to notify them of newly added or removed service listeners.
+ * The ListenerHook must be called in order: descending by service.ranking, then ascending by service.id.
+ * This is the natural order for ServiceReference.
+ *
+ * @param listeners A non-empty, unmodifiable collection of ListenerInfo objects.
+ * All elements in the list must be for the same bundle.
+ * @param added <code>true</code> if the specified listeners are being added. <code>false</code>
+ * if they are being removed.
+ */
+ private void notifyListenerHooks(final Collection<ListenerInfo> listeners, final boolean added) {
+ if (System.getSecurityManager() == null) {
+ notifyListenerHooksPrivileged(listeners, added);
+ } else {
+ AccessController.doPrivileged(new PrivilegedAction<Object>() {
+ public Object run() {
+ notifyListenerHooksPrivileged(listeners, added);
+ return null;
+ }
+ });
+ }
+
+ }
+
+ void notifyListenerHooksPrivileged(final Collection<ListenerInfo> listeners, final boolean added) {
+ assert !listeners.isEmpty();
+ if (Debug.DEBUG_HOOKS) {
+ Debug.println("notifyServiceListenerHooks(" + listeners + "," + (added ? "added" : "removed") + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
+ }
+
+ notifyHooksPrivileged(new HookContext() {
+ public void call(Object hook, ServiceRegistration<?> hookRegistration) throws Exception {
+ if (hook instanceof ListenerHook) {
+ if (added) {
+ ((ListenerHook) hook).added(listeners);
+ } else {
+ ((ListenerHook) hook).removed(listeners);
+ }
+ }
+ }
+
+ public String getHookClassName() {
+ return listenerHookName;
+ }
+
+ public String getHookMethodName() {
+ return added ? "added" : "removed"; //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ });
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ServiceUse.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ServiceUse.java
new file mode 100644
index 000000000..2f0bab55c
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ServiceUse.java
@@ -0,0 +1,294 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.serviceregistry;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import org.eclipse.osgi.framework.debug.Debug;
+import org.eclipse.osgi.framework.internal.core.BundleContextImpl;
+import org.eclipse.osgi.framework.internal.core.Msg;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.*;
+
+/**
+ * This class represents the use of a service by a bundle. One is created for each
+ * service acquired by a bundle. This class manages the calls to ServiceFactory
+ * and the bundle's use count.
+ *
+ * @ThreadSafe
+ */
+
+public class ServiceUse<S> {
+ /** ServiceFactory object if the service instance represents a factory,
+ null otherwise */
+ final ServiceFactory<S> factory;
+ /** BundleContext associated with this service use */
+ final BundleContextImpl context;
+ /** ServiceDescription of the registered service */
+ final ServiceRegistrationImpl<S> registration;
+
+ /** Service object either registered or that returned by
+ ServiceFactory.getService() */
+ /* @GuardedBy("this") */
+ private S cachedService;
+ /** bundle's use count for this service */
+ /* @GuardedBy("this") */
+ private int useCount;
+ /** true if we are calling the factory getService method. Used to detect recursion. */
+ /* @GuardedBy("this") */
+ private boolean factoryInUse;
+
+ /** Internal framework object. */
+
+ /**
+ * Constructs a service use encapsulating the service object.
+ * Objects of this class should be constructed while holding the
+ * registrations lock.
+ *
+ * @param context bundle getting the service
+ * @param registration ServiceRegistration of the service
+ */
+ ServiceUse(BundleContextImpl context, ServiceRegistrationImpl<S> registration) {
+ this.useCount = 0;
+ this.factoryInUse = false;
+ S service = registration.getServiceObject();
+ if (service instanceof ServiceFactory<?>) {
+ @SuppressWarnings("unchecked")
+ ServiceFactory<S> f = (ServiceFactory<S>) service;
+ this.factory = f;
+ this.cachedService = null;
+ } else {
+ this.factory = null;
+ this.cachedService = service;
+ }
+ this.context = context;
+ this.registration = registration;
+ }
+
+ /**
+ * Get a service's service object.
+ * Retrieves the service object for a service.
+ * A bundle's use of a service is tracked by a
+ * use count. Each time a service's service object is returned by
+ * {@link #getService}, the context bundle's use count for the service
+ * is incremented by one. Each time the service is release by
+ * {@link #ungetService}, the context bundle's use count
+ * for the service is decremented by one.
+ * When a bundle's use count for a service
+ * drops to zero, the bundle should no longer use the service.
+ *
+ * <p>The following steps are followed to get the service object:
+ * <ol>
+ * <li>The context bundle's use count for this service is incremented by one.
+ * <li>If the context bundle's use count for the service is now one and
+ * the service was registered with a {@link ServiceFactory},
+ * the {@link ServiceFactory#getService ServiceFactory.getService} method
+ * is called to create a service object for the context bundle.
+ * This service object is cached by the framework.
+ * While the context bundle's use count for the service is greater than zero,
+ * subsequent calls to get the services's service object for the context bundle
+ * will return the cached service object.
+ * <br>If the service object returned by the {@link ServiceFactory}
+ * is not an <code>instanceof</code>
+ * all the classes named when the service was registered or
+ * the {@link ServiceFactory} throws an exception,
+ * <code>null</code> is returned and a
+ * {@link FrameworkEvent} of type {@link FrameworkEvent#ERROR} is broadcast.
+ * <li>The service object for the service is returned.
+ * </ol>
+ *
+ * @return A service object for the service associated with this
+ * reference.
+ */
+ /* @GuardedBy("this") */
+ S getService() {
+ assert Thread.holdsLock(this);
+ if ((useCount > 0) || (factory == null)) {
+ if (useCount == Integer.MAX_VALUE) {
+ throw new ServiceException(Msg.SERVICE_USE_OVERFLOW);
+ }
+ useCount++;
+ return cachedService;
+ }
+
+ if (Debug.DEBUG_SERVICES) {
+ Debug.println("getService[factory=" + registration.getBundle() + "](" + context.getBundleImpl() + "," + registration + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ }
+ // check for recursive call
+ if (factoryInUse == true) {
+ if (Debug.DEBUG_SERVICES) {
+ Debug.println(factory + ".getService() recursively called."); //$NON-NLS-1$
+ }
+
+ ServiceException se = new ServiceException(NLS.bind(Msg.SERVICE_FACTORY_RECURSION, factory.getClass().getName(), "getService"), ServiceException.FACTORY_RECURSION); //$NON-NLS-1$
+ context.getFramework().publishFrameworkEvent(FrameworkEvent.WARNING, registration.getBundle(), se);
+ return null;
+ }
+ factoryInUse = true;
+ final S service;
+ try {
+ service = AccessController.doPrivileged(new PrivilegedAction<S>() {
+ public S run() {
+ return factory.getService(context.getBundleImpl(), registration);
+ }
+ });
+ } catch (Throwable t) {
+ if (Debug.DEBUG_SERVICES) {
+ Debug.println(factory + ".getService() exception: " + t.getMessage()); //$NON-NLS-1$
+ Debug.printStackTrace(t);
+ }
+ // allow the adaptor to handle this unexpected error
+ context.getFramework().getAdaptor().handleRuntimeError(t);
+ ServiceException se = new ServiceException(NLS.bind(Msg.SERVICE_FACTORY_EXCEPTION, factory.getClass().getName(), "getService"), ServiceException.FACTORY_EXCEPTION, t); //$NON-NLS-1$
+ context.getFramework().publishFrameworkEvent(FrameworkEvent.ERROR, registration.getBundle(), se);
+ return null;
+ } finally {
+ factoryInUse = false;
+ }
+
+ if (service == null) {
+ if (Debug.DEBUG_SERVICES) {
+ Debug.println(factory + ".getService() returned null."); //$NON-NLS-1$
+ }
+
+ ServiceException se = new ServiceException(NLS.bind(Msg.SERVICE_OBJECT_NULL_EXCEPTION, factory.getClass().getName()), ServiceException.FACTORY_ERROR);
+ context.getFramework().publishFrameworkEvent(FrameworkEvent.WARNING, registration.getBundle(), se);
+ return null;
+ }
+
+ String[] clazzes = registration.getClasses();
+ String invalidService = ServiceRegistry.checkServiceClass(clazzes, service);
+ if (invalidService != null) {
+ if (Debug.DEBUG_SERVICES) {
+ Debug.println("Service object is not an instanceof " + invalidService); //$NON-NLS-1$
+ }
+ ServiceException se = new ServiceException(NLS.bind(Msg.SERVICE_FACTORY_NOT_INSTANCEOF_CLASS_EXCEPTION, factory.getClass().getName(), invalidService), ServiceException.FACTORY_ERROR);
+ context.getFramework().publishFrameworkEvent(FrameworkEvent.ERROR, registration.getBundle(), se);
+ return null;
+ }
+
+ this.cachedService = service;
+ useCount++;
+
+ return service;
+ }
+
+ /**
+ * Unget a service's service object.
+ * Releases the service object for a service.
+ * If the context bundle's use count for the service is zero, this method
+ * returns <code>false</code>. Otherwise, the context bundle's use count for the
+ * service is decremented by one.
+ *
+ * <p>The service's service object
+ * should no longer be used and all references to it should be destroyed
+ * when a bundle's use count for the service
+ * drops to zero.
+ *
+ * <p>The following steps are followed to unget the service object:
+ * <ol>
+ * <li>If the context bundle's use count for the service is zero or
+ * the service has been unregistered,
+ * <code>false</code> is returned.
+ * <li>The context bundle's use count for this service is decremented by one.
+ * <li>If the context bundle's use count for the service is now zero and
+ * the service was registered with a {@link ServiceFactory},
+ * the {@link ServiceFactory#ungetService ServiceFactory.ungetService} method
+ * is called to release the service object for the context bundle.
+ * <li><code>true</code> is returned.
+ * </ol>
+ *
+ * @return <code>true</code> if the context bundle's use count for the service
+ * is zero otherwise <code>false</code>.
+ */
+ /* @GuardedBy("this") */
+ boolean ungetService() {
+ assert Thread.holdsLock(this);
+ if (useCount == 0) {
+ return true;
+ }
+
+ useCount--;
+ if (useCount > 0) {
+ return false;
+ }
+
+ if (factory == null) {
+ return true;
+ }
+
+ final S service = cachedService;
+ cachedService = null;
+
+ if (Debug.DEBUG_SERVICES) {
+ Debug.println("ungetService[factory=" + registration.getBundle() + "](" + context.getBundleImpl() + "," + registration + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ }
+ try {
+ AccessController.doPrivileged(new PrivilegedAction<S>() {
+ public S run() {
+ factory.ungetService(context.getBundleImpl(), registration, service);
+ return null;
+ }
+ });
+ } catch (Throwable t) {
+ if (Debug.DEBUG_GENERAL) {
+ Debug.println(factory + ".ungetService() exception"); //$NON-NLS-1$
+ Debug.printStackTrace(t);
+ }
+
+ ServiceException se = new ServiceException(NLS.bind(Msg.SERVICE_FACTORY_EXCEPTION, factory.getClass().getName(), "ungetService"), ServiceException.FACTORY_EXCEPTION, t); //$NON-NLS-1$
+ context.getFramework().publishFrameworkEvent(FrameworkEvent.ERROR, registration.getBundle(), se);
+ }
+
+ return true;
+ }
+
+ /**
+ * Release a service's service object.
+ * <ol>
+ * <li>The bundle's use count for this service is set to zero.
+ * <li>If the service was registered with a {@link ServiceFactory},
+ * the {@link ServiceFactory#ungetService ServiceFactory.ungetService} method
+ * is called to release the service object for the bundle.
+ * </ol>
+ */
+ /* @GuardedBy("this") */
+ void releaseService() {
+ assert Thread.holdsLock(this);
+ if ((useCount == 0) || (factory == null)) {
+ return;
+ }
+ final S service = cachedService;
+ cachedService = null;
+ useCount = 0;
+
+ if (Debug.DEBUG_SERVICES) {
+ Debug.println("releaseService[factory=" + registration.getBundle() + "](" + context.getBundleImpl() + "," + registration + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ }
+ try {
+ AccessController.doPrivileged(new PrivilegedAction<S>() {
+ public S run() {
+ factory.ungetService(context.getBundleImpl(), registration, service);
+ return null;
+ }
+ });
+ } catch (Throwable t) {
+ if (Debug.DEBUG_SERVICES) {
+ Debug.println(factory + ".ungetService() exception"); //$NON-NLS-1$
+ Debug.printStackTrace(t);
+ }
+
+ ServiceException se = new ServiceException(NLS.bind(Msg.SERVICE_FACTORY_EXCEPTION, factory.getClass().getName(), "ungetService"), ServiceException.FACTORY_EXCEPTION, t); //$NON-NLS-1$
+ context.getFramework().publishFrameworkEvent(FrameworkEvent.ERROR, registration.getBundle(), se);
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ShrinkableCollection.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ShrinkableCollection.java
new file mode 100644
index 000000000..2c2e6002f
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ShrinkableCollection.java
@@ -0,0 +1,196 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.serviceregistry;
+
+import java.util.*;
+
+/**
+ * A Shrinkable Collection. This class provides a wrapper for a list of collections
+ * that allows items to be removed from the wrapped collections (shrinking) but
+ * does not allow items to be added to the wrapped collections.
+ *
+ * <p>
+ * The collections must act as sets in that each collection in the list
+ * must not have two entries which are equal.
+ *
+ * <p>
+ * All the optional <code>Collection</code> operations except
+ * <code>add</code> and <code>addAll</code> are supported. Attempting to add to the
+ * collection will result in an <code>UnsupportedOperationException</code>.
+ *
+ */
+
+public class ShrinkableCollection<E> implements Collection<E> {
+ private final Collection<? extends E> collection;
+ private final List<Collection<? extends E>> list;
+
+ public ShrinkableCollection(Collection<? extends E> c) {
+ if (c == null) {
+ throw new NullPointerException();
+ }
+ @SuppressWarnings("unchecked")
+ List<Collection<? extends E>> empty = Collections.EMPTY_LIST;
+ list = empty;
+ collection = c;
+ }
+
+ public ShrinkableCollection(Collection<? extends E> c1, Collection<? extends E> c2) {
+ list = new ArrayList<Collection<? extends E>>(2);
+ list.add(c1);
+ list.add(c2);
+ collection = initComposite(list);
+ }
+
+ public ShrinkableCollection(List<Collection<? extends E>> l) {
+ list = new ArrayList<Collection<? extends E>>(l);
+ collection = initComposite(list);
+ }
+
+ private static <E> Collection<? extends E> initComposite(List<Collection<? extends E>> collections) {
+ int size = 0;
+ for (Collection<? extends E> c : collections) {
+ assert verifyNoDuplicates(c);
+ size += c.size();
+ }
+ Collection<E> result = new ArrayList<E>(size);
+ for (Collection<? extends E> c : collections) {
+ for (E e : c) {
+ if (!result.contains(e)) {
+ result.add(e);
+ }
+ }
+ }
+ return result;
+ }
+
+ private static <E> boolean verifyNoDuplicates(Collection<? extends E> c) {
+ for (E e : c) {
+ int count = 0;
+ for (E f : c) {
+ if (e == null) {
+ if (f == null) {
+ count++;
+ }
+ } else {
+ if (e.equals(f)) {
+ count++;
+ }
+ }
+ }
+ if (count != 1) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean add(E e) {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean addAll(Collection<? extends E> c) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void clear() {
+ collection.clear();
+ for (Collection<? extends E> c : list) {
+ c.clear();
+ }
+ }
+
+ public boolean contains(Object o) {
+ return collection.contains(o);
+ }
+
+ public boolean containsAll(Collection<?> c) {
+ return collection.containsAll(c);
+ }
+
+ public boolean isEmpty() {
+ return collection.isEmpty();
+ }
+
+ public Iterator<E> iterator() {
+ @SuppressWarnings("unchecked")
+ final Iterator<E> iter = (Iterator<E>) collection.iterator();
+ final List<Collection<? extends E>> collections = list;
+ if (collections.isEmpty()) {
+ return iter;
+ }
+ return new Iterator<E>() {
+ private E last;
+
+ public boolean hasNext() {
+ return iter.hasNext();
+ }
+
+ public E next() {
+ last = iter.next();
+ return last;
+ }
+
+ public void remove() {
+ iter.remove();
+ for (Collection<? extends E> c : collections) {
+ c.remove(last);
+ }
+ }
+ };
+ }
+
+ public boolean remove(Object o) {
+ final boolean result = collection.remove(o);
+ if (result) {
+ for (Collection<? extends E> c : list) {
+ c.remove(o);
+ }
+ }
+ return result;
+ }
+
+ public boolean removeAll(Collection<?> c) {
+ final boolean result = collection.removeAll(c);
+ if (result) {
+ for (Collection<? extends E> cc : list) {
+ cc.removeAll(c);
+ }
+ }
+ return result;
+ }
+
+ public boolean retainAll(Collection<?> c) {
+ final boolean result = collection.retainAll(c);
+ if (result) {
+ for (Collection<? extends E> cc : list) {
+ cc.retainAll(c);
+ }
+ }
+ return result;
+ }
+
+ public int size() {
+ return collection.size();
+ }
+
+ public Object[] toArray() {
+ return collection.toArray();
+ }
+
+ public <T> T[] toArray(T[] var0) {
+ return collection.toArray(var0);
+ }
+
+ public String toString() {
+ return collection.toString();
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ShrinkableEntrySetValueCollection.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ShrinkableEntrySetValueCollection.java
new file mode 100644
index 000000000..a98fdce71
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ShrinkableEntrySetValueCollection.java
@@ -0,0 +1,65 @@
+/*******************************************************************************
+ * Copyright (c) 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.serviceregistry;
+
+import java.util.*;
+
+public class ShrinkableEntrySetValueCollection<E> extends AbstractCollection<E> implements Collection<E> {
+ private final Set<? extends Map.Entry<?, ? extends E>> entrySet;
+
+ public ShrinkableEntrySetValueCollection(Set<? extends Map.Entry<?, ? extends E>> e) {
+ if (e == null) {
+ throw new NullPointerException();
+ }
+ entrySet = e;
+ }
+
+ public void clear() {
+ entrySet.clear();
+ }
+
+ public boolean isEmpty() {
+ return entrySet.isEmpty();
+ }
+
+ public Iterator<E> iterator() {
+ return new ValueIterator<E>(entrySet.iterator());
+ }
+
+ public int size() {
+ return entrySet.size();
+ }
+
+ /**
+ * Iterator over the values of the entry set.
+ */
+ static private final class ValueIterator<E> implements Iterator<E> {
+ private final Iterator<? extends Map.Entry<?, ? extends E>> iter;
+
+ ValueIterator(Iterator<? extends Map.Entry<?, ? extends E>> i) {
+ iter = i;
+ }
+
+ public boolean hasNext() {
+ return iter.hasNext();
+ }
+
+ public E next() {
+ final Map.Entry<?, ? extends E> entry = iter.next();
+ return entry.getValue();
+ }
+
+ public void remove() {
+ iter.remove();
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ShrinkableValueCollectionMap.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ShrinkableValueCollectionMap.java
new file mode 100644
index 000000000..d9992605d
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/serviceregistry/ShrinkableValueCollectionMap.java
@@ -0,0 +1,191 @@
+/*******************************************************************************
+ * Copyright (c) 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.serviceregistry;
+
+import java.util.*;
+
+public class ShrinkableValueCollectionMap<K, V> extends AbstractMap<K, Collection<V>> implements Map<K, Collection<V>> {
+ final Map<? extends K, ? extends Set<? extends Map.Entry<?, ? extends V>>> map;
+ Map<Object, Collection<V>> values;
+
+ public ShrinkableValueCollectionMap(Map<? extends K, ? extends Set<? extends Map.Entry<?, ? extends V>>> m) {
+ if (m == null) {
+ throw new NullPointerException();
+ }
+ map = m;
+ values = null;
+ }
+
+ public void clear() {
+ map.clear();
+ if (values != null) {
+ values.clear();
+ }
+ }
+
+ public boolean containsKey(Object key) {
+ return map.containsKey(key);
+ }
+
+ public boolean containsValue(Object value) {
+ /* Since values are collections and the collection has identity equality,
+ * there is no way any value could be contained in this map unless that
+ * value has already been gotten from this map.
+ */
+ if (values == null) {
+ return false;
+ }
+ return values.containsValue(value);
+ }
+
+ public Set<Map.Entry<K, Collection<V>>> entrySet() {
+ return new EntrySet();
+ }
+
+ public Collection<V> get(Object key) {
+ Collection<V> value = null;
+ if (values != null) {
+ value = values.get(key);
+ }
+ if (value == null) {
+ Set<? extends Map.Entry<?, ? extends V>> entrySet = map.get(key);
+ if (entrySet == null) {
+ return null;
+ }
+ value = new ShrinkableEntrySetValueCollection<V>(entrySet);
+ if (values == null) {
+ values = new HashMap<Object, Collection<V>>(map.size());
+ }
+ values.put(key, value);
+ }
+ return value;
+ }
+
+ public boolean isEmpty() {
+ return map.isEmpty();
+ }
+
+ public Collection<V> remove(Object key) {
+ Set<? extends Map.Entry<?, ? extends V>> entrySet = map.remove(key);
+ Collection<V> value = null;
+ if (values != null) {
+ value = values.remove(key);
+ }
+ if ((value == null) && (entrySet != null)) {
+ value = new ShrinkableEntrySetValueCollection<V>(entrySet);
+ }
+ return value;
+ }
+
+ public int size() {
+ return map.size();
+ }
+
+ /**
+ * Set class used for entry sets.
+ */
+ private final class EntrySet extends AbstractSet<Map.Entry<K, Collection<V>>> {
+ EntrySet() {
+ super();
+ }
+
+ public Iterator<Map.Entry<K, Collection<V>>> iterator() {
+ return new EntryIterator();
+ }
+
+ public int size() {
+ return ShrinkableValueCollectionMap.this.size();
+ }
+ }
+
+ /**
+ * Iterator class used for entry sets.
+ */
+ private final class EntryIterator implements Iterator<Map.Entry<K, Collection<V>>> {
+ private final Iterator<? extends K> iter;
+ private K last;
+
+ EntryIterator() {
+ iter = map.keySet().iterator();
+ }
+
+ public boolean hasNext() {
+ return iter.hasNext();
+ }
+
+ public Map.Entry<K, Collection<V>> next() {
+ last = iter.next();
+ return new Entry(last);
+ }
+
+ public void remove() {
+ iter.remove();
+ if (values != null) {
+ values.remove(last);
+ }
+ }
+ }
+
+ /**
+ * This class represents the entry in this map.
+ */
+ private final class Entry implements Map.Entry<K, Collection<V>> {
+ private final K key;
+ private Collection<V> value;
+
+ Entry(final K k) {
+ key = k;
+ }
+
+ public K getKey() {
+ return key;
+ }
+
+ public Collection<V> getValue() {
+ if (value == null) {
+ value = ShrinkableValueCollectionMap.this.get(key);
+ }
+ return value;
+ }
+
+ public Collection<V> setValue(Collection<V> value) {
+ throw new UnsupportedOperationException(); // entries cannot be modified.
+ }
+
+ public String toString() {
+ return getKey() + "=" + getValue(); //$NON-NLS-1$
+ }
+
+ public int hashCode() {
+ return hash(getKey()) ^ hash(getValue());
+ }
+
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof Map.Entry)) {
+ return false;
+ }
+ Map.Entry<?, ?> other = (Map.Entry<?, ?>) obj;
+ return equality(getKey(), other.getKey()) && equality(getValue(), other.getValue());
+ }
+ }
+
+ static int hash(Object one) {
+ return (one == null) ? 0 : one.hashCode();
+ }
+
+ static boolean equality(Object one, Object two) {
+ return (one == null) ? (two == null) : one.equals(two);
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/BERProcessor.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/BERProcessor.java
new file mode 100644
index 000000000..57b985226
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/BERProcessor.java
@@ -0,0 +1,274 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.signedcontent;
+
+import java.math.BigInteger;
+import java.security.SignatureException;
+
+/**
+ * This is a simple class that processes BER structures. This class
+ * uses BER processing as outlined in X.690.
+ */
+public class BERProcessor {
+ /**
+ * This is the buffer that contains the BER structures that are being interrogated.
+ */
+ byte buffer[];
+ /**
+ * The offset into <code>buffer</code> to the start of the structure being interrogated.
+ * If the offset is -1 that means that we have read the last structure.
+ */
+ int offset;
+ /**
+ * The last valid offset in <code>buffer</code>.
+ */
+ int lastOffset;
+ /**
+ * The offset into <code>buffer</code> to the start of the content of the structure
+ * being interrogated.
+ */
+ int contentOffset;
+ /**
+ * The length of the content of the structure being interrogated.
+ */
+ int contentLength;
+ /**
+ * The offset into <code>buffer</code> of the end of the structure being interrogated.
+ */
+ int endOffset;
+ /**
+ * The class of the tag of the current structure.
+ */
+ int classOfTag;
+ static final int UNIVERSAL_TAGCLASS = 0;
+ static final int APPLICATION_TAGCLASS = 1;
+ static final int CONTEXTSPECIFIC_TAGCLASS = 2;
+ static final int PRIVATE_TAGCLASS = 3;
+
+ static final byte BOOLTAG = 1;
+ static final byte INTTAG = 2;
+ static final byte OIDTAG = 6;
+ static final byte SEQTAG = 16;
+ static final byte SETTAG = 17;
+ static final byte NULLTAG = 5;
+
+ /**
+ * Tagnames used in toString()
+ */
+ static final String tagNames[] = {"<null>", "boolean", "int", "bitstring", "octetstring", "null", "objid", "objdesc", "external", "real", "enum", "pdv", "utf8", "relobjid", "resv", "resv", "sequence", "set", "char string"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ //$NON-NLS-10$ //$NON-NLS-11$ //$NON-NLS-12$ //$NON-NLS-13$ //$NON-NLS-14$ //$NON-NLS-15$ //$NON-NLS-16$ //$NON-NLS-17$ //$NON-NLS-18$ //$NON-NLS-19$
+
+ /**
+ * True if this is a structure for a constructed encoding.
+ */
+ public boolean constructed;
+ /**
+ * The tag type. Note that X.690 specifies encodings for tags with values greater than 31,
+ * but currently this class does not handle these kinds of tags.
+ */
+ public byte tag;
+
+ /**
+ * Constructs a BERProcessor to operate on the passed buffer. The first structure in the
+ * buffer will be processed before this method returns.
+ *
+ * @param buffer the buffer containing the BER structures.
+ * @param offset the offset into <code>buffer</code> to the start of the first structure.
+ * @param len the length of the BER structure.
+ * @throws SignatureException
+ */
+ public BERProcessor(byte buffer[], int offset, int len) throws SignatureException {
+ this.buffer = buffer;
+ this.offset = offset;
+ lastOffset = len + offset;
+ processStructure();
+ }
+
+ /**
+ * Parse the structure found at the current <code>offset</code> into <code>buffer</code>.
+ * Most methods, constructor, and stepinto, will call this method automatically. If
+ * <code>offset</code> is modified outside of those methods, this method will need to
+ * be invoked.
+ */
+ public void processStructure() throws SignatureException {
+ // Don't process if we are at the end
+ if (offset == -1)
+ return;
+ endOffset = offset;
+ // section 8.1.2.2
+ classOfTag = (buffer[offset] & 0xff) >> 6;
+ // section 8.1.2.5
+ constructed = (buffer[offset] & 0x20) != 0;
+ // section 8.1.2.3
+ byte tagNumber = (byte) (buffer[offset] & 0x1f);
+ if (tagNumber < 32) {
+ tag = tagNumber;
+ endOffset = offset + 1;
+ } else {
+ throw new SignatureException("Can't handle tags > 32"); //$NON-NLS-1$
+ }
+ if ((buffer[endOffset] & 0x80) == 0) {
+ // section 8.1.3.4 (doing the short form of the length)
+ contentLength = buffer[endOffset];
+ endOffset++;
+ } else {
+ // section 8.1.3.5 (doing the long form of the length)
+ int octetCount = buffer[endOffset] & 0x7f;
+ if (octetCount > 3)
+ throw new SignatureException("ContentLength octet count too large: " + octetCount); //$NON-NLS-1$
+ contentLength = 0;
+ endOffset++;
+ for (int i = 0; i < octetCount; i++) {
+ contentLength <<= 8;
+ contentLength |= buffer[endOffset] & 0xff;
+ endOffset++;
+ }
+ // section 8.1.3.6 (doing the indefinite form
+ if (octetCount == 0)
+ contentLength = -1;
+ }
+ contentOffset = endOffset;
+ if (contentLength != -1)
+ endOffset += contentLength;
+ if (endOffset > lastOffset)
+ throw new SignatureException("Content length too large: " + endOffset + " > " + lastOffset); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ /**
+ * Returns a String representation of the current BER structure.
+ * @return a String representation of the current BER structure.
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ switch (classOfTag) {
+ case UNIVERSAL_TAGCLASS :
+ sb.append('U');
+ break;
+ case APPLICATION_TAGCLASS :
+ sb.append('A');
+ break;
+ case CONTEXTSPECIFIC_TAGCLASS :
+ sb.append('C');
+ break;
+ case PRIVATE_TAGCLASS :
+ sb.append('P');
+ break;
+ }
+ sb.append(constructed ? 'C' : 'P');
+ sb.append(" tag=" + tag); //$NON-NLS-1$
+ if (tag < tagNames.length) {
+ sb.append("(" + tagNames[tag] + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ sb.append(" len="); //$NON-NLS-1$
+ sb.append(contentLength);
+ switch (tag) {
+ case INTTAG :
+ sb.append(" value=" + getIntValue()); //$NON-NLS-1$
+ break;
+ case OIDTAG :
+ sb.append(" value="); //$NON-NLS-1$
+ int oid[] = getObjId();
+ for (int i = 0; i < oid.length; i++) {
+ if (i > 0)
+ sb.append('.');
+ sb.append(oid[i]);
+ }
+ }
+ if (tag == 12 || (tag >= 18 && tag <= 22) || (tag >= 25 && tag <= 30)) {
+ sb.append(" value="); //$NON-NLS-1$
+ sb.append(getString());
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Returns a BERProcessor for the content of the current structure.
+ * @throws SignatureException
+ */
+ public BERProcessor stepInto() throws SignatureException {
+ return new BERProcessor(buffer, contentOffset, contentLength);
+ }
+
+ public void stepOver() throws SignatureException {
+ offset = endOffset;
+ if (endOffset >= lastOffset) {
+ offset = -1;
+ return;
+ }
+ processStructure();
+ }
+
+ public boolean endOfSequence() {
+ return offset == -1;
+ }
+
+ /**
+ * Gets the content from the current structure as a String.
+ * @return the content from the current structure as a String.
+ */
+ public String getString() {
+ return new String(buffer, contentOffset, contentLength);
+ }
+
+ /**
+ * Gets the content from the current structure as an int.
+ * @return the content from the current structure as an int.
+ */
+ public BigInteger getIntValue() {
+ return new BigInteger(getBytes());
+ }
+
+ /**
+ * Gets the content from the current structure as an object id (int[]).
+ * @return the content from the current structure as an object id (int[]).
+ */
+ public int[] getObjId() {
+ // First count the ids
+ int count = 0;
+ for (int i = 0; i < contentLength; i++) {
+ // section 8.19.2
+ if ((buffer[contentOffset + i] & 0x80) == 0)
+ count++;
+ }
+ count++; // section 8.19.3
+ int oid[] = new int[count];
+ int index = 0;
+ int currentValue = 0;
+ for (int i = 0; i < contentLength; i++) {
+ currentValue <<= 7;
+ currentValue |= buffer[contentOffset + i] & 0x7f;
+ // section 8.19.2
+ if ((buffer[contentOffset + i] & 0x80) == 0) {
+ if (index == 0) {
+ // section 8.19.4 special processing
+ oid[index++] = currentValue / 40;
+ oid[index++] = currentValue % 40;
+ } else {
+ oid[index++] = currentValue;
+ }
+ currentValue = 0;
+ }
+ }
+ return oid;
+ }
+
+ /**
+ * Get a copy of the bytes in the content of the current structure.
+ * @return a copy of the bytes in the content of the current structure.
+ */
+ public byte[] getBytes() {
+ byte v[] = new byte[contentLength];
+ System.arraycopy(buffer, contentOffset, v, 0, contentLength);
+ return v;
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/Base64.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/Base64.java
new file mode 100644
index 000000000..32d09834f
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/Base64.java
@@ -0,0 +1,198 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.signedcontent;
+
+public class Base64 {
+
+ private static final byte equalSign = (byte) '=';
+
+ static char digits[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', //
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', //
+ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', //
+ 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
+
+ /**
+ * This method decodes the byte array in base 64 encoding into a char array
+ * Base 64 encoding has to be according to the specification given by the
+ * RFC 1521 (5.2).
+ *
+ * @param data the encoded byte array
+ * @return the decoded byte array
+ */
+ public static byte[] decode(byte[] data) {
+ if (data.length == 0)
+ return data;
+ int lastRealDataIndex = data.length - 1;
+ while (data[lastRealDataIndex] == equalSign)
+ lastRealDataIndex--;
+ // original data digit is 8 bits long, but base64 digit is 6 bits long
+ int padBytes = data.length - 1 - lastRealDataIndex;
+ int byteLength = data.length * 6 / 8 - padBytes;
+ byte[] result = new byte[byteLength];
+ // Each 4 bytes of input (encoded) we end up with 3 bytes of output
+ int dataIndex = 0;
+ int resultIndex = 0;
+ int allBits = 0;
+ // how many result chunks we can process before getting to pad bytes
+ int resultChunks = (lastRealDataIndex + 1) / 4;
+ for (int i = 0; i < resultChunks; i++) {
+ allBits = 0;
+ // Loop 4 times gathering input bits (4 * 6 = 24)
+ for (int j = 0; j < 4; j++)
+ allBits = (allBits << 6) | decodeDigit(data[dataIndex++]);
+ // Loop 3 times generating output bits (3 * 8 = 24)
+ for (int j = resultIndex + 2; j >= resultIndex; j--) {
+ result[j] = (byte) (allBits & 0xff); // Bottom 8 bits
+ allBits = allBits >>> 8;
+ }
+ resultIndex += 3; // processed 3 result bytes
+ }
+ // Now we do the extra bytes in case the original (non-encoded) data
+ // was not multiple of 3 bytes
+ switch (padBytes) {
+ case 1 :
+ // 1 pad byte means 3 (4-1) extra Base64 bytes of input, 18
+ // bits, of which only 16 are meaningful
+ // Or: 2 bytes of result data
+ allBits = 0;
+ // Loop 3 times gathering input bits
+ for (int j = 0; j < 3; j++)
+ allBits = (allBits << 6) | decodeDigit(data[dataIndex++]);
+ // NOTE - The code below ends up being equivalent to allBits =
+ // allBits>>>2
+ // But we code it in a non-optimized way for clarity
+ // The 4th, missing 6 bits are all 0
+ allBits = allBits << 6;
+ // The 3rd, missing 8 bits are all 0
+ allBits = allBits >>> 8;
+ // Loop 2 times generating output bits
+ for (int j = resultIndex + 1; j >= resultIndex; j--) {
+ result[j] = (byte) (allBits & 0xff); // Bottom 8
+ // bits
+ allBits = allBits >>> 8;
+ }
+ break;
+ case 2 :
+ // 2 pad bytes mean 2 (4-2) extra Base64 bytes of input, 12 bits
+ // of data, of which only 8 are meaningful
+ // Or: 1 byte of result data
+ allBits = 0;
+ // Loop 2 times gathering input bits
+ for (int j = 0; j < 2; j++)
+ allBits = (allBits << 6) | decodeDigit(data[dataIndex++]);
+ // NOTE - The code below ends up being equivalent to allBits =
+ // allBits>>>4
+ // But we code it in a non-optimized way for clarity
+ // The 3rd and 4th, missing 6 bits are all 0
+ allBits = allBits << 6;
+ allBits = allBits << 6;
+ // The 3rd and 4th, missing 8 bits are all 0
+ allBits = allBits >>> 8;
+ allBits = allBits >>> 8;
+ result[resultIndex] = (byte) (allBits & 0xff); // Bottom
+ // 8
+ // bits
+ break;
+ }
+ return result;
+ }
+
+ /**
+ * This method converts a Base 64 digit to its numeric value.
+ *
+ * @param data digit (character) to convert
+ * @return value for the digit
+ */
+ static int decodeDigit(byte data) {
+ char charData = (char) data;
+ if (charData <= 'Z' && charData >= 'A')
+ return charData - 'A';
+ if (charData <= 'z' && charData >= 'a')
+ return charData - 'a' + 26;
+ if (charData <= '9' && charData >= '0')
+ return charData - '0' + 52;
+ switch (charData) {
+ case '+' :
+ return 62;
+ case '/' :
+ return 63;
+ default :
+ throw new IllegalArgumentException("Invalid char to decode: " + data); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * This method encodes the byte array into a char array in base 64 according
+ * to the specification given by the RFC 1521 (5.2).
+ *
+ * @param data the encoded char array
+ * @return the byte array that needs to be encoded
+ */
+ public static byte[] encode(byte[] data) {
+ int sourceChunks = data.length / 3;
+ int len = ((data.length + 2) / 3) * 4;
+ byte[] result = new byte[len];
+ int extraBytes = data.length - (sourceChunks * 3);
+ // Each 4 bytes of input (encoded) we end up with 3 bytes of output
+ int dataIndex = 0;
+ int resultIndex = 0;
+ int allBits = 0;
+ for (int i = 0; i < sourceChunks; i++) {
+ allBits = 0;
+ // Loop 3 times gathering input bits (3 * 8 = 24)
+ for (int j = 0; j < 3; j++)
+ allBits = (allBits << 8) | (data[dataIndex++] & 0xff);
+ // Loop 4 times generating output bits (4 * 6 = 24)
+ for (int j = resultIndex + 3; j >= resultIndex; j--) {
+ result[j] = (byte) digits[(allBits & 0x3f)]; // Bottom
+ // 6
+ // bits
+ allBits = allBits >>> 6;
+ }
+ resultIndex += 4; // processed 4 result bytes
+ }
+ // Now we do the extra bytes in case the original (non-encoded) data
+ // is not multiple of 4 bytes
+ switch (extraBytes) {
+ case 1 :
+ allBits = data[dataIndex++]; // actual byte
+ allBits = allBits << 8; // 8 bits of zeroes
+ allBits = allBits << 8; // 8 bits of zeroes
+ // Loop 4 times generating output bits (4 * 6 = 24)
+ for (int j = resultIndex + 3; j >= resultIndex; j--) {
+ result[j] = (byte) digits[(allBits & 0x3f)]; // Bottom
+ // 6
+ // bits
+ allBits = allBits >>> 6;
+ }
+ // 2 pad tags
+ result[result.length - 1] = (byte) '=';
+ result[result.length - 2] = (byte) '=';
+ break;
+ case 2 :
+ allBits = data[dataIndex++]; // actual byte
+ allBits = (allBits << 8) | (data[dataIndex++] & 0xff); // actual
+ // byte
+ allBits = allBits << 8; // 8 bits of zeroes
+ // Loop 4 times generating output bits (4 * 6 = 24)
+ for (int j = resultIndex + 3; j >= resultIndex; j--) {
+ result[j] = (byte) digits[(allBits & 0x3f)]; // Bottom
+ // 6
+ // bits
+ allBits = allBits >>> 6;
+ }
+ // 1 pad tag
+ result[result.length - 1] = (byte) '=';
+ break;
+ }
+ return result;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/BundleInstallListener.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/BundleInstallListener.java
new file mode 100644
index 000000000..47c8066fe
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/BundleInstallListener.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.signedcontent;
+
+import org.eclipse.osgi.baseadaptor.BaseData;
+import org.eclipse.osgi.framework.internal.core.AbstractBundle;
+import org.eclipse.osgi.internal.provisional.service.security.AuthorizationEngine;
+import org.eclipse.osgi.signedcontent.SignedContent;
+import org.osgi.framework.*;
+
+public class BundleInstallListener implements SynchronousBundleListener {
+
+ public void bundleChanged(BundleEvent event) {
+ Bundle bundle = event.getBundle();
+ switch (event.getType()) {
+ case BundleEvent.UPDATED :
+ // fall through to INSTALLED
+ case BundleEvent.INSTALLED :
+ TrustEngineListener listener = TrustEngineListener.getInstance();
+ AuthorizationEngine authEngine = listener == null ? null : listener.getAuthorizationEngine();
+ if (authEngine != null) {
+ BaseData baseData = (BaseData) ((AbstractBundle) bundle).getBundleData();
+ SignedStorageHook hook = (SignedStorageHook) baseData.getStorageHook(SignedStorageHook.KEY);
+ SignedContent signedContent = hook != null ? hook.signedContent : null;
+ authEngine.authorize(signedContent, bundle);
+ }
+ break;
+ default :
+ break;
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/DigestedInputStream.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/DigestedInputStream.java
new file mode 100644
index 000000000..d244c1681
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/DigestedInputStream.java
@@ -0,0 +1,160 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.signedcontent;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.security.MessageDigest;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile;
+import org.eclipse.osgi.signedcontent.InvalidContentException;
+import org.eclipse.osgi.signedcontent.SignerInfo;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * This InputStream will calculate the digest of bytes as they are read. At the
+ * end of the InputStream, it will calculate the digests and throw an exception
+ * if the calculated digest do not match the expected digests.
+ */
+class DigestedInputStream extends FilterInputStream {
+ private final MessageDigest digests[];
+ private final byte result[][];
+ private final BundleEntry entry;
+ private final BundleFile bundleFile;
+ private long remaining;
+
+ /**
+ * Constructs an InputStream that uses another InputStream as a source and
+ * calculates the digest. At the end of the stream an exception will be
+ * thrown if the calculated digest doesn't match the passed digest.
+ *
+ * @param in the stream to use as an input source.
+ * @param signerInfos the signers.
+ * @param results the expected digest.
+ * @throws IOException
+ */
+ DigestedInputStream(BundleEntry entry, BundleFile bundleFile, SignerInfo[] signerInfos, byte results[][], long size) throws IOException {
+ super(entry.getInputStream());
+ this.entry = entry;
+ this.bundleFile = bundleFile;
+ this.remaining = size;
+ this.digests = new MessageDigest[signerInfos.length];
+ for (int i = 0; i < signerInfos.length; i++)
+ this.digests[i] = SignatureBlockProcessor.getMessageDigest(signerInfos[i].getMessageDigestAlgorithm());
+ this.result = results;
+ }
+
+ /**
+ * Not supported.
+ */
+ public synchronized void mark(int readlimit) {
+ // Noop, we don't want to support this
+ }
+
+ /**
+ * Always returns false.
+ */
+ public boolean markSupported() {
+ return false;
+ }
+
+ /**
+ * Read a byte from the InputStream. Digests are calculated on reads. At the
+ * end of the stream the calculated digests must match the expected digests.
+ *
+ * @return the character read or -1 at end of stream.
+ * @throws IOException if there was an problem reading the byte or at the
+ * end of the stream the calculated digests do not match the
+ * expected digests.
+ * @see java.io.InputStream#read()
+ */
+ public int read() throws IOException {
+ if (remaining <= 0)
+ return -1;
+ int c = super.read();
+ if (c != -1) {
+ for (int i = 0; i < digests.length; i++)
+ digests[i].update((byte) c);
+ remaining--;
+ } else {
+ // We hit eof so set remaining to zero
+ remaining = 0;
+ }
+ if (remaining == 0)
+ verifyDigests();
+ return c;
+ }
+
+ private void verifyDigests() throws InvalidContentException {
+ // Check the digest at end of file
+ for (int i = 0; i < digests.length; i++) {
+ byte rc[] = digests[i].digest();
+ if (!MessageDigest.isEqual(result[i], rc))
+ throw new InvalidContentException(NLS.bind(SignedContentMessages.File_In_Jar_Is_Tampered, entry.getName(), bundleFile.getBaseFile()), null);
+ }
+ }
+
+ /**
+ * Read bytes from the InputStream. Digests are calculated on reads. At the
+ * end of the stream the calculated digests must match the expected digests.
+ *
+ * @return the number of characters read or -1 at end of stream.
+ * @throws IOException if there was an problem reading or at the
+ * end of the stream the calculated digests do not match the
+ * expected digests.
+ * @see java.io.InputStream#read()
+ */
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (remaining <= 0)
+ return -1;
+ int rc = super.read(b, off, len);
+ if (rc != -1) {
+ for (int i = 0; i < digests.length; i++)
+ digests[i].update(b, off, rc);
+ remaining -= rc;
+ } else {
+ // We hit eof so set remaining to zero
+ remaining = 0;
+ }
+ if (remaining <= 0)
+ verifyDigests();
+ return rc;
+ }
+
+ /**
+ * Not supported.
+ *
+ * @throws IOException always thrown if this method is called since mark/reset is not supported.
+ * @see java.io.InputStream#reset()
+ */
+ public synchronized void reset() throws IOException {
+ // Throw IOException, we don't want to support this
+ throw new IOException("Reset not supported"); //$NON-NLS-1$
+ }
+
+ /**
+ * This method is implemented as a read into a bitbucket.
+ */
+ public long skip(long n) throws IOException {
+ byte buffer[] = new byte[4096];
+ long count = 0;
+ while (n - count > 0) {
+ int rc = (n - count) > buffer.length ? buffer.length : (int) (n - count);
+ rc = read(buffer, 0, rc);
+ if (rc == -1)
+ break;
+ count += rc;
+ n -= rc;
+ }
+ return count;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/LegacyVerifierFactory.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/LegacyVerifierFactory.java
new file mode 100644
index 000000000..d0ecae651
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/LegacyVerifierFactory.java
@@ -0,0 +1,137 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 IBM Corporation and others. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors: IBM Corporation - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.osgi.internal.signedcontent;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.cert.*;
+import java.util.*;
+import org.eclipse.osgi.internal.provisional.verifier.*;
+import org.eclipse.osgi.signedcontent.*;
+import org.osgi.framework.Bundle;
+
+public class LegacyVerifierFactory implements CertificateVerifierFactory {
+ private final SignedContentFactory signedContentFactory;
+
+ public LegacyVerifierFactory(SignedContentFactory signedContentFactory) {
+ this.signedContentFactory = signedContentFactory;
+ }
+
+ public CertificateVerifier getVerifier(File content) throws IOException {
+ try {
+ return new LegacyVerifier(signedContentFactory.getSignedContent(content));
+ } catch (GeneralSecurityException e) {
+ throw (IOException) new IOException(e.getMessage()).initCause(e);
+ }
+ }
+
+ public CertificateVerifier getVerifier(Bundle bundle) throws IOException {
+ try {
+ return new LegacyVerifier(signedContentFactory.getSignedContent(bundle));
+ } catch (GeneralSecurityException e) {
+ throw (IOException) new IOException(e.getMessage()).initCause(e);
+ }
+ }
+
+ static class LegacyVerifier implements CertificateVerifier {
+ private final SignedContent signedContent;
+
+ public LegacyVerifier(SignedContent signedContent) {
+ this.signedContent = signedContent;
+ }
+
+ public void checkContent() throws CertificateException, CertificateExpiredException {
+ SignedContentEntry[] entries = signedContent.getSignedEntries();
+ for (int i = 0; i < entries.length; i++) {
+ try {
+ entries[i].verify();
+ } catch (InvalidContentException e) {
+ throw (SecurityException) new SecurityException(e.getMessage()).initCause(e);
+ } catch (IOException e) {
+ throw (SecurityException) new SecurityException(e.getMessage()).initCause(e);
+ }
+ }
+ SignerInfo[] infos = signedContent.getSignerInfos();
+ for (int i = 0; i < infos.length; i++)
+ signedContent.checkValidity(infos[i]);
+ }
+
+ public CertificateChain[] getChains() {
+ SignerInfo infos[] = signedContent.getSignerInfos();
+ CertificateChain[] chains = new CertificateChain[infos.length];
+ for (int i = 0; i < chains.length; i++)
+ chains[i] = new LegacyChain(infos[i], signedContent);
+ return chains;
+ }
+
+ public boolean isSigned() {
+ return signedContent.isSigned();
+ }
+
+ public String[] verifyContent() {
+ List<String> invalidContent = new ArrayList<String>(0);
+ SignedContentEntry[] entries = signedContent.getSignedEntries();
+ for (int i = 0; i < entries.length; i++) {
+ try {
+ entries[i].verify();
+ } catch (InvalidContentException e) {
+ invalidContent.add(entries[i].getName());
+ } catch (IOException e) {
+ invalidContent.add(entries[i].getName());
+ }
+ }
+ return invalidContent.toArray(new String[invalidContent.size()]);
+ }
+ }
+
+ static class LegacyChain implements CertificateChain {
+ private final SignerInfo signerInfo;
+ private final SignedContent content;
+
+ public LegacyChain(SignerInfo signerInfo, SignedContent content) {
+ this.signerInfo = signerInfo;
+ this.content = content;
+ }
+
+ public Certificate[] getCertificates() {
+ return signerInfo.getCertificateChain();
+ }
+
+ public String getChain() {
+ StringBuffer sb = new StringBuffer();
+ Certificate[] certs = getCertificates();
+ for (int i = 0; i < certs.length; i++) {
+ X509Certificate x509Cert = ((X509Certificate) certs[i]);
+ sb.append(x509Cert.getSubjectDN().getName());
+ sb.append("; "); //$NON-NLS-1$
+ }
+ return sb.toString();
+ }
+
+ public Certificate getRoot() {
+ Certificate[] certs = getCertificates();
+ return certs.length > 0 ? certs[certs.length - 1] : null;
+ }
+
+ public Certificate getSigner() {
+ Certificate[] certs = getCertificates();
+ return certs.length > 0 ? certs[0] : null;
+ }
+
+ public Date getSigningTime() {
+ return content.getSigningTime(signerInfo);
+ }
+
+ public boolean isTrusted() {
+ return signerInfo.isTrusted();
+ }
+
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/PKCS7DateParser.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/PKCS7DateParser.java
new file mode 100644
index 000000000..29a82bfa1
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/PKCS7DateParser.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2010 IBM Corporation and others. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors: IBM Corporation - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.osgi.internal.signedcontent;
+
+import java.security.*;
+import java.security.cert.CertificateException;
+import java.util.*;
+
+public class PKCS7DateParser {
+
+ static Date parseDate(PKCS7Processor pkcs7Processor, String signer, String file) throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, SignatureException, NoSuchProviderException {
+ Map<int[], byte[]> unsignedAttrs = pkcs7Processor.getUnsignedAttrs();
+ if (unsignedAttrs != null) {
+ // get the timestamp construct
+ byte[] timeStampConstruct = retrieveTimeStampConstruct(unsignedAttrs);
+
+ // there is a timestamp in the signer info
+ if (timeStampConstruct != null) {
+ PKCS7Processor timestampProcess = new PKCS7Processor(timeStampConstruct, 0, timeStampConstruct.length, signer, file);
+ timestampProcess.verifyCerts();
+ pkcs7Processor.setTSACertificates(timestampProcess.getCertificates());
+ return timestampProcess.getSigningTime();
+ }
+ }
+ return null;
+ }
+
+ private static byte[] retrieveTimeStampConstruct(Map<int[], byte[]> unsignedAttrs) {
+ Set<int[]> objIDs = unsignedAttrs.keySet();
+ Iterator<int[]> iter = objIDs.iterator();
+ while (iter.hasNext()) {
+ int[] objID = iter.next();
+ if (Arrays.equals(SignedContentConstants.TIMESTAMP_OID, objID)) {
+ return unsignedAttrs.get(objID);
+ }
+ }
+ return null;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/PKCS7Processor.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/PKCS7Processor.java
new file mode 100644
index 000000000..a1267a8e1
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/PKCS7Processor.java
@@ -0,0 +1,484 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2012 IBM Corporation and others. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors: IBM Corporation - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.osgi.internal.signedcontent;
+
+import java.io.ByteArrayInputStream;
+import java.math.BigInteger;
+import java.security.*;
+import java.security.cert.*;
+import java.security.cert.Certificate;
+import java.text.*;
+import java.util.*;
+import javax.security.auth.x500.X500Principal;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * This class processes a PKCS7 file. See RFC 2315 for specifics.
+ */
+public class PKCS7Processor implements SignedContentConstants {
+
+ static CertificateFactory certFact;
+
+ static {
+ try {
+ certFact = CertificateFactory.getInstance("X.509"); //$NON-NLS-1$
+ } catch (CertificateException e) {
+ SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e);
+ }
+ }
+
+ private final String signer;
+ private final String file;
+
+ private Certificate[] certificates;
+ private Certificate[] tsaCertificates;
+
+ // key(object id) = value(structure)
+ private Map<int[], byte[]> signedAttrs;
+
+ // key(object id) = value(structure)
+ private Map<int[], byte[]> unsignedAttrs;
+
+ // store the signature of a signerinfo
+ private byte signature[];
+ private String digestAlgorithm;
+ private String signatureAlgorithm;
+
+ private Certificate signerCert;
+ private Date signingTime;
+
+ private static String oid2String(int oid[]) {
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < oid.length; i++) {
+ if (i > 0)
+ sb.append('.');
+ sb.append(oid[i]);
+ }
+ return sb.toString();
+ }
+
+ private static String findEncryption(int encOid[]) throws NoSuchAlgorithmException {
+ if (Arrays.equals(DSA_OID, encOid)) {
+ return "DSA"; //$NON-NLS-1$
+ }
+ if (Arrays.equals(RSA_OID, encOid)) {
+ return "RSA"; //$NON-NLS-1$
+ }
+ throw new NoSuchAlgorithmException("No algorithm found for " + oid2String(encOid)); //$NON-NLS-1$
+ }
+
+ private static String findDigest(int digestOid[]) throws NoSuchAlgorithmException {
+ if (Arrays.equals(SHA1_OID, digestOid)) {
+ return SHA1_STR;
+ }
+ if (Arrays.equals(SHA224_OID, digestOid)) {
+ return SHA224_STR;
+ }
+ if (Arrays.equals(SHA256_OID, digestOid)) {
+ return SHA256_STR;
+ }
+ if (Arrays.equals(SHA384_OID, digestOid)) {
+ return SHA384_STR;
+ }
+ if (Arrays.equals(SHA512_OID, digestOid)) {
+ return SHA512_STR;
+ }
+ if (Arrays.equals(SHA512_224_OID, digestOid)) {
+ return SHA512_224_STR;
+ }
+ if (Arrays.equals(SHA512_256_OID, digestOid)) {
+ return SHA512_256_STR;
+ }
+ if (Arrays.equals(MD5_OID, digestOid)) {
+ return MD5_STR;
+ }
+ if (Arrays.equals(MD2_OID, digestOid)) {
+ return MD2_STR;
+ }
+ throw new NoSuchAlgorithmException("No algorithm found for " + oid2String(digestOid)); //$NON-NLS-1$
+ }
+
+ public PKCS7Processor(byte pkcs7[], int pkcs7Offset, int pkcs7Length, String signer, String file) throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, SignatureException, NoSuchProviderException {
+ this.signer = signer;
+ this.file = file;
+ // First grab the certificates
+ List<Certificate> certs = null;
+
+ BERProcessor bp = new BERProcessor(pkcs7, pkcs7Offset, pkcs7Length);
+
+ // Just do a sanity check and make sure we are actually doing a PKCS7
+ // stream
+ // PKCS7: Step into the ContentType
+ bp = bp.stepInto();
+ if (!Arrays.equals(bp.getObjId(), SIGNEDDATA_OID)) {
+ throw new SignatureException(NLS.bind(SignedContentMessages.PKCS7_Invalid_File, signer, file));
+ }
+
+ // PKCS7: Process the SignedData structure
+ bp.stepOver(); // (**wrong comments**) skip over the oid
+ bp = bp.stepInto(); // go into the Signed data
+ bp = bp.stepInto(); // It is a structure;
+ bp.stepOver(); // Yeah, yeah version = 1
+ bp.stepOver(); // We'll see the digest stuff again; digestAlgorithms
+
+ // process the encapContentInfo structure
+ processEncapContentInfo(bp);
+
+ bp.stepOver();
+
+ // PKCS7: check if the class tag is 0
+ if (bp.classOfTag == BERProcessor.CONTEXTSPECIFIC_TAGCLASS && bp.tag == 0) {
+ // process the certificate elements inside the signeddata strcuture
+ certs = processCertificates(bp);
+ }
+
+ if (certs == null || certs.size() < 1)
+ throw new SignatureException("There are no certificates in the .RSA/.DSA file!"); //$NON-NLS-1$
+
+ // Okay, here are our certificates.
+ bp.stepOver();
+ if (bp.classOfTag == BERProcessor.UNIVERSAL_TAGCLASS && bp.tag == 1) {
+ bp.stepOver(); // Don't use the CRLs if present
+ }
+
+ processSignerInfos(bp, certs);
+
+ // construct the cert path
+ certs = constructCertPath(certs, signerCert);
+
+ // initialize the certificates
+ certificates = certs.toArray(new Certificate[certs.size()]);
+ verifyCerts();
+ // if this pkcs7process is tsa asn.1 block, the signingTime should already be set
+ if (signingTime == null)
+ signingTime = PKCS7DateParser.parseDate(this, signer, file);
+ }
+
+ private void processEncapContentInfo(BERProcessor bp) throws SignatureException {
+ // check immediately if TSTInfo is there
+ BERProcessor encapContentBERS = bp.stepInto();
+ if (Arrays.equals(encapContentBERS.getObjId(), TIMESTAMP_TST_OID)) {
+
+ // eContent
+ encapContentBERS.stepOver();
+ BERProcessor encapContentBERS1 = encapContentBERS.stepInto();
+
+ // obtain eContent octet structure
+ byte bytesman[] = encapContentBERS1.getBytes();
+ BERProcessor eContentStructure = new BERProcessor(bytesman, 0, bytesman.length);
+
+ // pointing at 'version Integer' now
+ BERProcessor eContentBER = eContentStructure.stepInto();
+ int tsaVersion = eContentBER.getIntValue().intValue();
+
+ if (tsaVersion != 1)
+ throw new SignatureException("Not a version 1 time-stamp token"); //$NON-NLS-1$
+
+ // policty : TSAPolicyId
+ eContentBER.stepOver();
+
+ // messageImprint : MessageImprint
+ eContentBER.stepOver();
+
+ // serialNumber : INTEGER
+ eContentBER.stepOver();
+
+ // genTime : GeneralizedTime
+ eContentBER.stepOver();
+
+ // check time ends w/ 'Z'
+ String dateString = new String(eContentBER.getBytes());
+ if (!dateString.endsWith("Z")) //$NON-NLS-1$
+ throw new SignatureException("Wrong dateformat used in time-stamp token"); //$NON-NLS-1$
+
+ // create the appropriate date time string format
+ // date format could be yyyyMMddHHmmss[.s...]Z or yyyyMMddHHmmssZ
+ int dotIndex = dateString.indexOf('.');
+ StringBuffer dateFormatSB = new StringBuffer("yyyyMMddHHmmss"); //$NON-NLS-1$
+ if (dotIndex != -1) {
+ // yyyyMMddHHmmss[.s...]Z, find out number of s in the bracket
+ int noS = dateString.indexOf('Z') - 1 - dotIndex;
+ dateFormatSB.append('.');
+
+ // append s
+ for (int i = 0; i < noS; i++) {
+ dateFormatSB.append('s');
+ }
+ }
+ dateFormatSB.append("'Z'"); //$NON-NLS-1$
+
+ try {
+ // if the current locale is th_TH, or ja_JP_JP, then our dateFormat object will end up with
+ // a calendar such as Buddhist or Japanese Imperial Calendar, and the signing time will be
+ // incorrect ... so always use English as the locale for parsing the time, resulting in a
+ // Gregorian calendar
+ DateFormat dateFormt = new SimpleDateFormat(dateFormatSB.toString(), Locale.ENGLISH);
+ dateFormt.setTimeZone(TimeZone.getTimeZone("GMT")); //$NON-NLS-1$
+ signingTime = dateFormt.parse(dateString);
+ } catch (ParseException e) {
+ throw (SignatureException) new SignatureException(SignedContentMessages.PKCS7_Parse_Signing_Time).initCause(e);
+ }
+ }
+ }
+
+ private List<Certificate> constructCertPath(List<Certificate> certs, Certificate targetCert) {
+ List<Certificate> certsList = new ArrayList<Certificate>();
+ certsList.add(targetCert);
+
+ X509Certificate currentCert = (X509Certificate) targetCert;
+ int numIteration = certs.size();
+ int i = 0;
+ while (i < numIteration) {
+
+ X500Principal subject = currentCert.getSubjectX500Principal();
+ X500Principal issuer = currentCert.getIssuerX500Principal();
+
+ if (subject.equals(issuer)) {
+ // the cert path has been constructed
+ break;
+ }
+
+ currentCert = null;
+ Iterator<Certificate> itr = certs.iterator();
+
+ while (itr.hasNext()) {
+ X509Certificate tempCert = (X509Certificate) itr.next();
+
+ if (tempCert.getSubjectX500Principal().equals(issuer)) {
+ certsList.add(tempCert);
+ currentCert = tempCert;
+ }
+ }
+
+ i++;
+ }
+
+ return certsList;
+ }
+
+ public void verifyCerts() throws InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException {
+ if (certificates == null || certificates.length == 0) {
+ throw new CertificateException("There are no certificates in the signature block file!"); //$NON-NLS-1$
+ }
+
+ int len = certificates.length;
+
+ // check the certs validity and signatures
+ for (int i = 0; i < len; i++) {
+ X509Certificate currentX509Cert = (X509Certificate) certificates[i];
+ if (i == len - 1) {
+ if (currentX509Cert.getSubjectDN().equals(currentX509Cert.getIssuerDN()))
+ currentX509Cert.verify(currentX509Cert.getPublicKey());
+ } else {
+ X509Certificate nextX509Cert = (X509Certificate) certificates[i + 1];
+ currentX509Cert.verify(nextX509Cert.getPublicKey());
+ }
+ }
+ }
+
+ private Certificate processSignerInfos(BERProcessor bp, List<Certificate> certs) throws CertificateException, NoSuchAlgorithmException, SignatureException {
+ // We assume there is only one SingerInfo element
+
+ // PKCS7: SignerINFOS processing
+ bp = bp.stepInto(); // Step into the set of signerinfos
+ bp = bp.stepInto(); // Step into the signerinfo sequence
+
+ // make sure the version is 1
+ BigInteger signerInfoVersion = bp.getIntValue();
+ if (signerInfoVersion.intValue() != 1) {
+ throw new CertificateException(SignedContentMessages.PKCS7_SignerInfo_Version_Not_Supported);
+ }
+
+ // PKCS7: version CMSVersion
+ bp.stepOver(); // Skip the version
+
+ // PKCS7: sid [SignerIdentifier : issuerAndSerialNumber or subjectKeyIdentifer]
+ BERProcessor issuerAndSN = bp.stepInto();
+ X500Principal signerIssuer = new X500Principal(new ByteArrayInputStream(issuerAndSN.buffer, issuerAndSN.offset, issuerAndSN.endOffset - issuerAndSN.offset));
+ issuerAndSN.stepOver();
+ BigInteger sn = issuerAndSN.getIntValue();
+
+ // initilize the newSignerCert to the issuer cert of leaf cert
+ Certificate newSignerCert = null;
+
+ Iterator<Certificate> itr = certs.iterator();
+ // PKCS7: compuare the issuers in the issuerAndSN BER equals to the issuers in Certs generated at the beginning of this method
+ // it seems like there is no neeed, cause both ways use the same set of bytes
+ while (itr.hasNext()) {
+ X509Certificate cert = (X509Certificate) itr.next();
+ if (cert.getIssuerX500Principal().equals(signerIssuer) && cert.getSerialNumber().equals(sn)) {
+ newSignerCert = cert;
+ break;
+ }
+ }
+
+ if (newSignerCert == null)
+ throw new CertificateException("Signer certificate not in pkcs7block"); //$NON-NLS-1$
+
+ // set the signer cert
+ signerCert = newSignerCert;
+
+ // PKCS7: skip over the sid [SignerIdentifier : issuerAndSerialNumber or subjectKeyIdentifer]
+ bp.stepOver(); // skip the issuer name and serial number
+
+ // PKCS7: digestAlgorithm DigestAlgorithmIdentifier
+ BERProcessor digestAlg = bp.stepInto();
+ digestAlgorithm = findDigest(digestAlg.getObjId());
+
+ // PKCS7: check if the next one if context class for signedAttrs
+ bp.stepOver(); // skip the digest alg
+
+ // process the signed attributes if there is any
+ processSignedAttributes(bp);
+
+ // PKCS7: signatureAlgorithm for this SignerInfo
+ BERProcessor encryptionAlg = bp.stepInto();
+ signatureAlgorithm = findEncryption(encryptionAlg.getObjId());
+ bp.stepOver(); // skip the encryption alg
+
+ // PKCS7: signature
+ signature = bp.getBytes();
+
+ // PKCS7: Step into the unsignedAttrs,
+ bp.stepOver();
+
+ // process the unsigned attributes if there is any
+ processUnsignedAttributes(bp);
+
+ return newSignerCert;
+ }
+
+ private void processUnsignedAttributes(BERProcessor bp) throws SignatureException {
+
+ if (bp.classOfTag == BERProcessor.CONTEXTSPECIFIC_TAGCLASS && bp.tag == 1) {
+
+ // there are some unsignedAttrs are found!!
+ unsignedAttrs = new HashMap<int[], byte[]>();
+
+ // step into a set of unsigned attributes, I believe, when steps
+ // into here, the 'poiter' is pointing to the first element
+ BERProcessor unsignedAttrsBERS = bp.stepInto();
+ do {
+ // process the unsignedAttrsBER by getting the attr type first,
+ // then the strcuture for the type
+ BERProcessor unsignedAttrBER = unsignedAttrsBERS.stepInto();
+
+ // check if it is timestamp attribute type
+ int[] objID = unsignedAttrBER.getObjId();
+ // if(Arrays.equals(TIMESTAMP_OID, objID)) {
+ // System.out.println("This is a timestamp type, to continue");
+ // }
+
+ // get the structure for the attribute type
+ unsignedAttrBER.stepOver();
+ byte[] structure = unsignedAttrBER.getBytes();
+ unsignedAttrs.put(objID, structure);
+ unsignedAttrsBERS.stepOver();
+ } while (!unsignedAttrsBERS.endOfSequence());
+ }
+ }
+
+ private void processSignedAttributes(BERProcessor bp) throws SignatureException {
+ if (bp.classOfTag == BERProcessor.CONTEXTSPECIFIC_TAGCLASS) {
+
+ // process the signed attributes
+ signedAttrs = new HashMap<int[], byte[]>();
+
+ BERProcessor signedAttrsBERS = bp.stepInto();
+ do {
+ BERProcessor signedAttrBER = signedAttrsBERS.stepInto();
+ int[] signedAttrObjID = signedAttrBER.getObjId();
+
+ // step over to the attribute value
+ signedAttrBER.stepOver();
+
+ byte[] signedAttrStructure = signedAttrBER.getBytes();
+
+ signedAttrs.put(signedAttrObjID, signedAttrStructure);
+
+ signedAttrsBERS.stepOver();
+ } while (!signedAttrsBERS.endOfSequence());
+ bp.stepOver();
+ }
+ }
+
+ public Certificate[] getCertificates() {
+ return certificates == null ? new Certificate[0] : certificates;
+ }
+
+ public void verifySFSignature(byte data[], int dataOffset, int dataLength) throws InvalidKeyException, NoSuchAlgorithmException, SignatureException {
+ Signature sig = Signature.getInstance(digestAlgorithm + "with" + signatureAlgorithm); //$NON-NLS-1$
+ sig.initVerify(signerCert.getPublicKey());
+ sig.update(data, dataOffset, dataLength);
+ if (!sig.verify(signature)) {
+ throw new SignatureException(NLS.bind(SignedContentMessages.Signature_Not_Verify, signer, file));
+ }
+ }
+
+ /**
+ * Return a map of signed attributes, the key(objid) = value(PKCSBlock in bytes for the key)
+ *
+ * @return map if there is any signed attributes, null otherwise
+ */
+ public Map<int[], byte[]> getUnsignedAttrs() {
+ return unsignedAttrs;
+ }
+
+ /**
+ * Return a map of signed attributes, the key(objid) = value(PKCSBlock in bytes for the key)
+ *
+ * @return map if there is any signed attributes, null otherwise
+ */
+ public Map<int[], byte[]> getSignedAttrs() {
+ return signedAttrs;
+ }
+
+ /**
+ *
+ * @param bp
+ * @return a List of certificates from target cert to root cert in order
+ *
+ * @throws CertificateException
+ * @throws SignatureException
+ */
+ private List<Certificate> processCertificates(BERProcessor bp) throws CertificateException, SignatureException {
+ List<Certificate> rtvList = new ArrayList<Certificate>(3);
+
+ // Step into the first certificate-element
+ BERProcessor certsBERS = bp.stepInto();
+
+ do {
+ X509Certificate x509Cert = (X509Certificate) certFact.generateCertificate(new ByteArrayInputStream(certsBERS.buffer, certsBERS.offset, certsBERS.endOffset - certsBERS.offset));
+
+ if (x509Cert != null) {
+ rtvList.add(x509Cert);
+ }
+
+ // go to the next cert element
+ certsBERS.stepOver();
+ } while (!certsBERS.endOfSequence());
+
+ // Collections.reverse(rtvList);
+ return rtvList;
+ }
+
+ public Date getSigningTime() {
+ return signingTime;
+ }
+
+ void setTSACertificates(Certificate[] tsaCertificates) {
+ this.tsaCertificates = tsaCertificates;
+ }
+
+ public Certificate[] getTSACertificates() {
+ return (tsaCertificates == null) ? new Certificate[0] : tsaCertificates;
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignatureBlockProcessor.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignatureBlockProcessor.java
new file mode 100644
index 000000000..8f9684147
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignatureBlockProcessor.java
@@ -0,0 +1,499 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2012 IBM Corporation and others. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors: IBM Corporation - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.osgi.internal.signedcontent;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.*;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.util.*;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+import org.eclipse.osgi.signedcontent.SignerInfo;
+import org.eclipse.osgi.util.NLS;
+
+public class SignatureBlockProcessor implements SignedContentConstants {
+ private final SignedBundleFile signedBundle;
+ private List<SignerInfo> signerInfos = new ArrayList<SignerInfo>();
+ private Map<String, Object> contentMDResults = new HashMap<String, Object>();
+ // map of tsa singers keyed by SignerInfo -> {tsa_SignerInfo, signingTime}
+ private Map<SignerInfo, Object[]> tsaSignerInfos;
+ private final int supportFlags;
+
+ public SignatureBlockProcessor(SignedBundleFile signedContent, int supportFlags) {
+ this.signedBundle = signedContent;
+ this.supportFlags = supportFlags;
+ }
+
+ public SignedContentImpl process() throws IOException, InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException {
+ BundleFile wrappedBundleFile = signedBundle.getWrappedBundleFile();
+ BundleEntry be = wrappedBundleFile.getEntry(META_INF_MANIFEST_MF);
+ if (be == null)
+ return createUnsignedContent();
+
+ // read all the signature block file names into a list
+ Enumeration<String> en = wrappedBundleFile.getEntryPaths(META_INF);
+ List<String> signers = new ArrayList<String>(2);
+ while (en.hasMoreElements()) {
+ String name = en.nextElement();
+ if ((name.endsWith(DOT_DSA) || name.endsWith(DOT_RSA)) && name.indexOf('/') == name.lastIndexOf('/'))
+ signers.add(name);
+ }
+
+ // this means the jar is not signed
+ if (signers.size() == 0)
+ return createUnsignedContent();
+
+ byte manifestBytes[] = readIntoArray(be);
+ // process the signers
+
+ for (Iterator<String> iSigners = signers.iterator(); iSigners.hasNext();)
+ processSigner(wrappedBundleFile, manifestBytes, iSigners.next());
+
+ // done processing now create a SingedContent to return
+ SignerInfo[] allSigners = signerInfos.toArray(new SignerInfo[signerInfos.size()]);
+ for (Iterator<Map.Entry<String, Object>> iResults = contentMDResults.entrySet().iterator(); iResults.hasNext();) {
+ Map.Entry<String, Object> entry = iResults.next();
+ @SuppressWarnings("unchecked")
+ List<Object>[] value = (List<Object>[]) entry.getValue();
+ SignerInfo[] entrySigners = value[0].toArray(new SignerInfo[value[0].size()]);
+ byte[][] entryResults = value[1].toArray(new byte[value[1].size()][]);
+ entry.setValue(new Object[] {entrySigners, entryResults});
+ }
+ SignedContentImpl result = new SignedContentImpl(allSigners, (supportFlags & SignedBundleHook.VERIFY_RUNTIME) != 0 ? contentMDResults : null);
+ result.setContent(signedBundle);
+ result.setTSASignerInfos(tsaSignerInfos);
+ return result;
+ }
+
+ private SignedContentImpl createUnsignedContent() {
+ SignedContentImpl result = new SignedContentImpl(new SignerInfo[0], contentMDResults);
+ result.setContent(signedBundle);
+ return result;
+ }
+
+ private void processSigner(BundleFile bf, byte[] manifestBytes, String signer) throws IOException, SignatureException, InvalidKeyException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException {
+ BundleEntry be = bf.getEntry(signer);
+ byte pkcs7Bytes[] = readIntoArray(be);
+ int dotIndex = signer.lastIndexOf('.');
+ be = bf.getEntry(signer.substring(0, dotIndex) + DOT_SF);
+ byte sfBytes[] = readIntoArray(be);
+
+ // Step 1, verify the .SF file is signed by the private key that corresponds to the public key
+ // in the .RSA/.DSA file
+ String baseFile = bf.getBaseFile() != null ? bf.getBaseFile().toString() : null;
+ PKCS7Processor processor = new PKCS7Processor(pkcs7Bytes, 0, pkcs7Bytes.length, signer, baseFile);
+ // call the Step 1 in the Jar File Verification algorithm
+ processor.verifySFSignature(sfBytes, 0, sfBytes.length);
+ // algorithm used
+ String digAlg = getDigAlgFromSF(sfBytes);
+ if (digAlg == null)
+ throw new SignatureException(NLS.bind(SignedContentMessages.SF_File_Parsing_Error, new String[] {bf.toString()}));
+ // get the digest results
+ // Process the Step 2 in the Jar File Verification algorithm
+ // Get the manifest out of the signature file and make sure
+ // it matches MANIFEST.MF
+ verifyManifestAndSignatureFile(manifestBytes, sfBytes);
+
+ // create a SignerInfo with the processed information
+ SignerInfoImpl signerInfo = new SignerInfoImpl(processor.getCertificates(), null, digAlg);
+ if ((supportFlags & SignedBundleHook.VERIFY_RUNTIME) != 0)
+ // only populate the manifests digest information for verifying content at runtime
+ populateMDResults(manifestBytes, signerInfo);
+ signerInfos.add(signerInfo);
+ // check for tsa signers
+ Certificate[] tsaCerts = processor.getTSACertificates();
+ Date signingTime = processor.getSigningTime();
+ if (tsaCerts != null && signingTime != null) {
+ SignerInfoImpl tsaSignerInfo = new SignerInfoImpl(tsaCerts, null, digAlg);
+ if (tsaSignerInfos == null)
+ tsaSignerInfos = new HashMap<SignerInfo, Object[]>(2);
+ tsaSignerInfos.put(signerInfo, new Object[] {tsaSignerInfo, signingTime});
+ }
+ }
+
+ /**
+ * Verify the digest listed in each entry in the .SF file with corresponding section in the manifest
+ * @throws SignatureException
+ */
+ private void verifyManifestAndSignatureFile(byte[] manifestBytes, byte[] sfBytes) throws SignatureException {
+
+ String sf = new String(sfBytes);
+ sf = stripContinuations(sf);
+
+ // check if there -Digest-Manfiest: header in the file
+ int off = sf.indexOf(digestManifestSearch);
+ if (off != -1) {
+ int start = sf.lastIndexOf('\n', off);
+ String manifestDigest = null;
+ if (start != -1) {
+ // Signature-Version has to start the file, so there
+ // should always be a newline at the start of
+ // Digest-Manifest
+ String digestName = sf.substring(start + 1, off);
+ if (digestName.equalsIgnoreCase(MD5_STR))
+ manifestDigest = calculateDigest(getMessageDigest(MD5_STR), manifestBytes);
+ else if (digestName.equalsIgnoreCase(SHA1_STR))
+ manifestDigest = calculateDigest(getMessageDigest(SHA1_STR), manifestBytes);
+ else
+ manifestDigest = calculateDigest(getMessageDigest(digestName), manifestBytes);
+ off += digestManifestSearchLen;
+
+ // find out the index of first '\n' after the -Digest-Manifest:
+ int nIndex = sf.indexOf('\n', off);
+ String digestValue = sf.substring(off, nIndex - 1);
+
+ // check if the the computed digest value of manifest file equals to the digest value in the .sf file
+ if (!digestValue.equals(manifestDigest)) {
+ SignatureException se = new SignatureException(NLS.bind(SignedContentMessages.Security_File_Is_Tampered, new String[] {signedBundle.getBaseFile().toString()}));
+ SignedBundleHook.log(se.getMessage(), FrameworkLogEntry.ERROR, se);
+ throw se;
+ }
+ }
+ }
+ }
+
+ private void populateMDResults(byte mfBuf[], SignerInfo signerInfo) throws NoSuchAlgorithmException {
+ // need to make a string from the MF file data bytes
+ String mfStr = new String(mfBuf);
+
+ // start parsing each entry in the MF String
+ int entryStartOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME);
+ int length = mfStr.length();
+
+ while ((entryStartOffset != -1) && (entryStartOffset < length)) {
+
+ // get the start of the next 'entry', i.e. the end of this entry
+ int entryEndOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME, entryStartOffset + 1);
+ if (entryEndOffset == -1) {
+ // if there is no next entry, then the end of the string
+ // is the end of this entry
+ entryEndOffset = mfStr.length();
+ }
+
+ // get the string for this entry only, since the entryStartOffset
+ // points to the '\n' befor the 'Name: ' we increase it by 1
+ // this is guaranteed to not go past end-of-string and be less
+ // then entryEndOffset.
+ String entryStr = mfStr.substring(entryStartOffset + 1, entryEndOffset);
+ entryStr = stripContinuations(entryStr);
+
+ // entry points to the start of the next 'entry'
+ String entryName = getEntryFileName(entryStr);
+
+ // if we could retrieve an entry name, then we will extract
+ // digest type list, and the digest value list
+ if (entryName != null) {
+
+ String aDigestLine = getDigestLine(entryStr, signerInfo.getMessageDigestAlgorithm());
+
+ if (aDigestLine != null) {
+ String msgDigestAlgorithm = getDigestAlgorithmFromString(aDigestLine);
+ if (!msgDigestAlgorithm.equalsIgnoreCase(signerInfo.getMessageDigestAlgorithm()))
+ continue; // TODO log error?
+ byte digestResult[] = getDigestResultsList(aDigestLine);
+
+ //
+ // only insert this entry into the table if its
+ // "well-formed",
+ // i.e. only if we could extract its name, digest types, and
+ // digest-results
+ //
+ // sanity check, if the 2 lists are non-null, then their
+ // counts must match
+ //
+ // if ((msgDigestObj != null) && (digestResultsList != null)
+ // && (1 != digestResultsList.length)) {
+ // throw new RuntimeException(
+ // "Errors occurs when parsing the manifest file stream!"); //$NON-NLS-1$
+ // }
+ @SuppressWarnings("unchecked")
+ List<Object>[] mdResult = (List<Object>[]) contentMDResults.get(entryName);
+ if (mdResult == null) {
+ @SuppressWarnings("unchecked")
+ List<Object>[] arrayLists = new ArrayList[2];
+ mdResult = arrayLists;
+ mdResult[0] = new ArrayList<Object>();
+ mdResult[1] = new ArrayList<Object>();
+ contentMDResults.put(entryName, mdResult);
+ }
+ mdResult[0].add(signerInfo);
+ mdResult[1].add(digestResult);
+ } // could get lines of digest entries in this MF file entry
+ } // could retrieve entry name
+ // increment the offset to the ending entry...
+ entryStartOffset = entryEndOffset;
+ }
+ }
+
+ private static byte[] getDigestResultsList(String digestLines) {
+ byte resultsList[] = null;
+ if (digestLines != null) {
+ // for each digest-line retrieve the digest result
+ // for (int i = 0; i < digestLines.length; i++) {
+ String sDigestLine = digestLines;
+ int indexDigest = sDigestLine.indexOf(MF_DIGEST_PART);
+ indexDigest += MF_DIGEST_PART.length();
+ // if there is no data to extract for this digest value
+ // then we will fail...
+ if (indexDigest >= sDigestLine.length()) {
+ resultsList = null;
+ // break;
+ }
+ // now attempt to base64 decode the result
+ String sResult = sDigestLine.substring(indexDigest);
+ try {
+ resultsList = Base64.decode(sResult.getBytes());
+ } catch (Throwable t) {
+ // malformed digest result, no longer processing this entry
+ resultsList = null;
+ }
+ }
+ return resultsList;
+ }
+
+ private static String getDigestAlgorithmFromString(String digestLines) throws NoSuchAlgorithmException {
+ if (digestLines != null) {
+ // String sDigestLine = digestLines[i];
+ int indexDigest = digestLines.indexOf(MF_DIGEST_PART);
+ String sDigestAlgType = digestLines.substring(0, indexDigest);
+ if (sDigestAlgType.equalsIgnoreCase(MD5_STR)) {
+ // remember the "algorithm type"
+ return MD5_STR;
+ } else if (sDigestAlgType.equalsIgnoreCase(SHA1_STR)) {
+ // remember the "algorithm type" object
+ return SHA1_STR;
+ } else {
+ return sDigestAlgType;
+ }
+ }
+ return null;
+ }
+
+ private static String getEntryFileName(String manifestEntry) {
+ // get the beginning of the name
+ int nameStart = manifestEntry.indexOf(MF_ENTRY_NAME);
+ if (nameStart == -1) {
+ return null;
+ }
+ // check where the name ends
+ int nameEnd = manifestEntry.indexOf('\n', nameStart);
+ if (nameEnd == -1) {
+ return null;
+ }
+ // if there is a '\r' before the '\n', then we'll strip it
+ if (manifestEntry.charAt(nameEnd - 1) == '\r') {
+ nameEnd--;
+ }
+ // get to the beginning of the actual name...
+ nameStart += MF_ENTRY_NAME.length();
+ if (nameStart >= nameEnd) {
+ return null;
+ }
+ return manifestEntry.substring(nameStart, nameEnd);
+ }
+
+ /**
+ * Returns the Base64 encoded digest of the passed set of bytes.
+ */
+ private static String calculateDigest(MessageDigest digest, byte[] bytes) {
+ return new String(Base64.encode(digest.digest(bytes)));
+ }
+
+ static synchronized MessageDigest getMessageDigest(String algorithm) {
+ try {
+ return MessageDigest.getInstance(algorithm);
+ } catch (NoSuchAlgorithmException e) {
+ SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e);
+ }
+ return null;
+ }
+
+ /**
+ * Read the .SF file abd assuming that same digest algorithm will be used through out the whole
+ * .SF file. That digest algorithm name in the last entry will be returned.
+ *
+ * @param SFBuf a .SF file in bytes
+ * @return the digest algorithm name used in the .SF file
+ */
+ private static String getDigAlgFromSF(byte SFBuf[]) {
+ // need to make a string from the MF file data bytes
+ String mfStr = new String(SFBuf);
+ String entryStr = null;
+
+ // start parsing each entry in the MF String
+ int entryStartOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME);
+ int length = mfStr.length();
+
+ while ((entryStartOffset != -1) && (entryStartOffset < length)) {
+
+ // get the start of the next 'entry', i.e. the end of this entry
+ int entryEndOffset = mfStr.indexOf(MF_ENTRY_NEWLN_NAME, entryStartOffset + 1);
+ if (entryEndOffset == -1) {
+ // if there is no next entry, then the end of the string
+ // is the end of this entry
+ entryEndOffset = mfStr.length();
+ }
+
+ // get the string for this entry only, since the entryStartOffset
+ // points to the '\n' befor the 'Name: ' we increase it by 1
+ // this is guaranteed to not go past end-of-string and be less
+ // then entryEndOffset.
+ entryStr = mfStr.substring(entryStartOffset + 1, entryEndOffset);
+ entryStr = stripContinuations(entryStr);
+ break;
+ }
+
+ if (entryStr != null) {
+ // process the entry to retrieve the digest algorith name
+ String digestLine = getDigestLine(entryStr, null);
+
+ // throw parsing
+ return getMessageDigestName(digestLine);
+ }
+ return null;
+ }
+
+ /**
+ *
+ * @param manifestEntry contains a single MF file entry of the format
+ * "Name: foo"
+ * "MD5-Digest: [base64 encoded MD5 digest data]"
+ * "SHA1-Digest: [base64 encoded SHA1 digest dat]"
+ *
+ * @param desireDigestAlg a string representing the desire digest value to be returned if there are
+ * multiple digest lines.
+ * If this value is null, return whatever digest value is in the entry.
+ *
+ * @return this function returns a digest line based on the desire digest algorithm value
+ * (since only MD5 and SHA1 are recognized here),
+ * or a 'null' will be returned if none of the digest algorithms
+ * were recognized.
+ */
+ private static String getDigestLine(String manifestEntry, String desireDigestAlg) {
+ String result = null;
+
+ // find the first digest line
+ int indexDigest = manifestEntry.indexOf(MF_DIGEST_PART);
+ // if we didn't find any digests at all, then we are done
+ if (indexDigest == -1)
+ return null;
+
+ // while we continue to find digest entries
+ // note: in the following loop we bail if any of the lines
+ // look malformed...
+ while (indexDigest != -1) {
+ // see where this digest line begins (look to left)
+ int indexStart = manifestEntry.lastIndexOf('\n', indexDigest);
+ if (indexStart == -1)
+ return null;
+ // see where it ends (look to right)
+ int indexEnd = manifestEntry.indexOf('\n', indexDigest);
+ if (indexEnd == -1)
+ return null;
+ // strip off ending '\r', if any
+ int indexEndToUse = indexEnd;
+ if (manifestEntry.charAt(indexEndToUse - 1) == '\r')
+ indexEndToUse--;
+ // indexStart points to the '\n' before this digest line
+ int indexStartToUse = indexStart + 1;
+ if (indexStartToUse >= indexEndToUse)
+ return null;
+
+ // now this may be a valid digest line, parse it a bit more
+ // to see if this is a preferred digest algorithm
+ String digestLine = manifestEntry.substring(indexStartToUse, indexEndToUse);
+ String digAlg = getMessageDigestName(digestLine);
+ if (desireDigestAlg != null) {
+ if (desireDigestAlg.equalsIgnoreCase(digAlg))
+ return digestLine;
+ }
+
+ // desireDigestAlg is null, always return the digestLine
+ result = digestLine;
+
+ // iterate to next digest line in this entry
+ indexDigest = manifestEntry.indexOf(MF_DIGEST_PART, indexEnd);
+ }
+
+ // if we couldn't find any digest lines, then we are done
+ return result;
+ }
+
+ /**
+ * Return the Message Digest name
+ *
+ * @param digLine the message digest line is in the following format. That is in the
+ * following format:
+ * DIGEST_NAME-digest: digest value
+ * @return a string representing a message digest.
+ */
+ private static String getMessageDigestName(String digLine) {
+ String rtvValue = null;
+ if (digLine != null) {
+ int indexDigest = digLine.indexOf(MF_DIGEST_PART);
+ if (indexDigest != -1) {
+ rtvValue = digLine.substring(0, indexDigest);
+ }
+ }
+ return rtvValue;
+ }
+
+ private static String stripContinuations(String entry) {
+ if (entry.indexOf("\n ") < 0 && entry.indexOf("\r ") < 0) //$NON-NLS-1$//$NON-NLS-2$
+ return entry;
+ StringBuffer buffer = new StringBuffer(entry);
+ removeAll(buffer, "\r\n "); //$NON-NLS-1$
+ removeAll(buffer, "\n "); //$NON-NLS-1$
+ removeAll(buffer, "\r "); //$NON-NLS-1$
+ return buffer.toString();
+ }
+
+ private static StringBuffer removeAll(StringBuffer buffer, String toRemove) {
+ int index = buffer.indexOf(toRemove);
+ int length = toRemove.length();
+ while (index > 0) {
+ buffer.replace(index, index + length, ""); //$NON-NLS-1$
+ index = buffer.indexOf(toRemove, index);
+ }
+ return buffer;
+ }
+
+ private static byte[] readIntoArray(BundleEntry be) throws IOException {
+ int size = (int) be.getSize();
+ InputStream is = be.getInputStream();
+ try {
+ byte b[] = new byte[size];
+ int rc = readFully(is, b);
+ if (rc != size) {
+ throw new IOException("Couldn't read all of " + be.getName() + ": " + rc + " != " + size); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return b;
+ } finally {
+ try {
+ is.close();
+ } catch (IOException e) {
+ // do nothing;
+ }
+ }
+ }
+
+ private static int readFully(InputStream is, byte b[]) throws IOException {
+ int count = b.length;
+ int offset = 0;
+ int rc;
+ while ((rc = is.read(b, offset, count)) > 0) {
+ count -= rc;
+ offset += rc;
+ }
+ return offset;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedBundleFile.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedBundleFile.java
new file mode 100644
index 000000000..a271261c4
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedBundleFile.java
@@ -0,0 +1,218 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.signedcontent;
+
+import java.io.*;
+import java.net.URL;
+import java.security.*;
+import java.security.cert.*;
+import java.security.cert.Certificate;
+import java.util.Date;
+import java.util.Enumeration;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+import org.eclipse.osgi.service.security.TrustEngine;
+import org.eclipse.osgi.signedcontent.*;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * This class wraps a Repository of classes and resources to check and enforce
+ * signatures. It requires full signing of the manifest by all signers. If no
+ * signatures are found, the classes and resources are retrieved without checks.
+ */
+public class SignedBundleFile extends BundleFile implements SignedContentConstants, SignedContent {
+ private BundleFile wrappedBundleFile;
+ SignedContentImpl signedContent;
+ private final int supportFlags;
+
+ SignedBundleFile(SignedContentImpl signedContent, int supportFlags) {
+ this.signedContent = signedContent;
+ this.supportFlags = supportFlags;
+ }
+
+ void setBundleFile(BundleFile bundleFile) throws IOException, InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException {
+ wrappedBundleFile = bundleFile;
+ if (signedContent == null) {
+ SignatureBlockProcessor signatureProcessor = new SignatureBlockProcessor(this, supportFlags);
+ signedContent = signatureProcessor.process();
+ if (signedContent != null)
+ determineTrust(signedContent, supportFlags);
+ }
+ }
+
+ static void determineTrust(SignedContentImpl trustedContent, int supportFlags) {
+ TrustEngine[] engines = null;
+ SignerInfo[] signers = trustedContent.getSignerInfos();
+ for (int i = 0; i < signers.length; i++) {
+ // first check if we need to find an anchor
+ if (signers[i].getTrustAnchor() == null) {
+ // no anchor set ask the trust engines
+ if (engines == null)
+ engines = SignedBundleHook.getTrustEngines();
+ // check trust of singer certs
+ Certificate[] signerCerts = signers[i].getCertificateChain();
+ ((SignerInfoImpl) signers[i]).setTrustAnchor(findTrustAnchor(signerCerts, engines, supportFlags));
+ // if signer has a tsa check trust of tsa certs
+ SignerInfo tsaSignerInfo = trustedContent.getTSASignerInfo(signers[i]);
+ if (tsaSignerInfo != null) {
+ Certificate[] tsaCerts = tsaSignerInfo.getCertificateChain();
+ ((SignerInfoImpl) tsaSignerInfo).setTrustAnchor(findTrustAnchor(tsaCerts, engines, supportFlags));
+ }
+ }
+ }
+ }
+
+ private static Certificate findTrustAnchor(Certificate[] certs, TrustEngine[] engines, int supportFlags) {
+ if ((supportFlags & SignedBundleHook.VERIFY_TRUST) == 0)
+ // we are not searching the engines; in this case we just assume the root cert is trusted
+ return certs != null && certs.length > 0 ? certs[certs.length - 1] : null;
+ for (int i = 0; i < engines.length; i++) {
+ try {
+ Certificate anchor = engines[i].findTrustAnchor(certs);
+ if (anchor != null)
+ // found an anchor
+ return anchor;
+ } catch (IOException e) {
+ // log the exception and continue
+ SignedBundleHook.log("TrustEngine failure: " + engines[i].getName(), FrameworkLogEntry.WARNING, e); //$NON-NLS-1$
+ }
+ }
+ return null;
+ }
+
+ public File getFile(String path, boolean nativeCode) {
+ return wrappedBundleFile.getFile(path, nativeCode);
+ }
+
+ public BundleEntry getEntry(String path) {
+ // strip off leading slashes so we can ensure the path matches the one provided in the manifest.
+ if (path.length() > 0 && path.charAt(0) == '/')
+ path = path.substring(1);
+ BundleEntry be = wrappedBundleFile.getEntry(path);
+ if ((supportFlags & SignedBundleHook.VERIFY_RUNTIME) == 0 || signedContent == null)
+ return be;
+ if (path.startsWith(META_INF)) {
+ int lastSlash = path.lastIndexOf('/');
+ if (lastSlash == META_INF.length() - 1) {
+ if (path.equals(META_INF_MANIFEST_MF) || path.endsWith(DOT_DSA) || path.endsWith(DOT_RSA) || path.endsWith(DOT_SF) || path.indexOf(SIG_DASH) == META_INF.length())
+ return be;
+ SignedContentEntry signedEntry = signedContent.getSignedEntry(path);
+ if (signedEntry == null)
+ // TODO this is to allow 1.4 signed bundles to work, it would be better if we could detect 1.4 signed bundles and only do this for them.
+ return be;
+ }
+ }
+ if (be == null) {
+ // double check that no signer thinks it should exist
+ SignedContentEntry signedEntry = signedContent.getSignedEntry(path);
+ if (signedEntry != null)
+ throw new SecurityException(NLS.bind(SignedContentMessages.file_is_removed_from_jar, path, getBaseFile().toString()));
+ return null;
+ }
+ return new SignedBundleEntry(be);
+ }
+
+ public Enumeration<String> getEntryPaths(String path) {
+ return wrappedBundleFile.getEntryPaths(path);
+ }
+
+ public void close() throws IOException {
+ wrappedBundleFile.close();
+ }
+
+ public void open() throws IOException {
+ wrappedBundleFile.open();
+ }
+
+ public boolean containsDir(String dir) {
+ return wrappedBundleFile.containsDir(dir);
+ }
+
+ public File getBaseFile() {
+ return wrappedBundleFile.getBaseFile();
+ }
+
+ class SignedBundleEntry extends BundleEntry {
+ BundleEntry nestedEntry;
+
+ SignedBundleEntry(BundleEntry nestedEntry) {
+ this.nestedEntry = nestedEntry;
+ }
+
+ public InputStream getInputStream() throws IOException {
+ InputStream in = signedContent.getDigestInputStream(nestedEntry);
+ if (in == null)
+ throw new SecurityException("Corrupted file: the digest does not exist for the file " + nestedEntry.getName()); //$NON-NLS-1$
+ return in;
+ }
+
+ public long getSize() {
+ return nestedEntry.getSize();
+ }
+
+ public String getName() {
+ return nestedEntry.getName();
+ }
+
+ public long getTime() {
+ return nestedEntry.getTime();
+ }
+
+ public URL getLocalURL() {
+ return nestedEntry.getLocalURL();
+ }
+
+ public URL getFileURL() {
+ return nestedEntry.getFileURL();
+ }
+
+ }
+
+ BundleFile getWrappedBundleFile() {
+ return wrappedBundleFile;
+ }
+
+ SignedContentImpl getSignedContent() {
+ return signedContent;
+ }
+
+ public SignedContentEntry[] getSignedEntries() {
+ return signedContent == null ? null : signedContent.getSignedEntries();
+ }
+
+ public SignedContentEntry getSignedEntry(String name) {
+ return signedContent == null ? null : signedContent.getSignedEntry(name);
+ }
+
+ public SignerInfo[] getSignerInfos() {
+ return signedContent == null ? null : signedContent.getSignerInfos();
+ }
+
+ public Date getSigningTime(SignerInfo signerInfo) {
+ return signedContent == null ? null : signedContent.getSigningTime(signerInfo);
+ }
+
+ public SignerInfo getTSASignerInfo(SignerInfo signerInfo) {
+ return signedContent == null ? null : signedContent.getTSASignerInfo(signerInfo);
+ }
+
+ public boolean isSigned() {
+ return signedContent == null ? false : signedContent.isSigned();
+ }
+
+ public void checkValidity(SignerInfo signerInfo) throws CertificateExpiredException, CertificateNotYetValidException {
+ if (signedContent != null)
+ signedContent.checkValidity(signerInfo);
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedBundleHook.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedBundleHook.java
new file mode 100644
index 000000000..58e417df1
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedBundleHook.java
@@ -0,0 +1,350 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.signedcontent;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.*;
+import java.security.*;
+import java.security.cert.CertificateException;
+import java.util.*;
+import org.eclipse.osgi.baseadaptor.*;
+import org.eclipse.osgi.baseadaptor.bundlefile.*;
+import org.eclipse.osgi.baseadaptor.hooks.AdaptorHook;
+import org.eclipse.osgi.baseadaptor.hooks.BundleFileWrapperFactoryHook;
+import org.eclipse.osgi.framework.adaptor.BundleData;
+import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor;
+import org.eclipse.osgi.framework.internal.core.*;
+import org.eclipse.osgi.framework.log.FrameworkLog;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+import org.eclipse.osgi.internal.provisional.service.security.AuthorizationEngine;
+import org.eclipse.osgi.internal.provisional.verifier.CertificateVerifierFactory;
+import org.eclipse.osgi.internal.service.security.DefaultAuthorizationEngine;
+import org.eclipse.osgi.internal.service.security.KeyStoreTrustEngine;
+import org.eclipse.osgi.service.security.TrustEngine;
+import org.eclipse.osgi.signedcontent.SignedContent;
+import org.eclipse.osgi.signedcontent.SignedContentFactory;
+import org.eclipse.osgi.util.ManifestElement;
+import org.eclipse.osgi.util.NLS;
+import org.osgi.framework.*;
+import org.osgi.framework.Constants;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * Implements signed bundle hook support for the framework
+ */
+public class SignedBundleHook implements AdaptorHook, BundleFileWrapperFactoryHook, HookConfigurator, SignedContentFactory {
+ static final int VERIFY_CERTIFICATE = 0x01;
+ static final int VERIFY_TRUST = 0x02;
+ static final int VERIFY_RUNTIME = 0x04;
+ static final int VERIFY_AUTHORITY = 0x08;
+ static final int VERIFY_ALL = VERIFY_CERTIFICATE | VERIFY_TRUST | VERIFY_RUNTIME | VERIFY_AUTHORITY;
+ private static String SUPPORT_CERTIFICATE = "certificate"; //$NON-NLS-1$
+ private static String SUPPORT_TRUST = "trust"; //$NON-NLS-1$
+ private static String SUPPORT_RUNTIME = "runtime"; //$NON-NLS-1$
+ private static String SUPPORT_AUTHORITY = "authority"; //$NON-NLS-1$
+ private static String SUPPORT_ALL = "all"; //$NON-NLS-1$
+ private static String SUPPORT_TRUE = "true"; //$NON-NLS-1$
+
+ //TODO: comes from configuration!;
+ private static String CACERTS_PATH = System.getProperty("java.home") + File.separatorChar + "lib" + File.separatorChar + "security" + File.separatorChar + "cacerts"; //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$//$NON-NLS-4$
+ private static String CACERTS_TYPE = "JKS"; //$NON-NLS-1$
+ private static ServiceTracker<TrustEngine, TrustEngine> trustEngineTracker;
+ private static BaseAdaptor ADAPTOR;
+ private static String SIGNED_BUNDLE_SUPPORT = "osgi.support.signature.verify"; //$NON-NLS-1$
+ private static String SIGNED_CONTENT_SUPPORT = "osgi.signedcontent.support"; //$NON-NLS-1$
+ private static String OSGI_KEYSTORE = "osgi.framework.keystore"; //$NON-NLS-1$
+ private static int supportSignedBundles;
+ private TrustEngineListener trustEngineListener;
+ private BundleInstallListener installListener;
+ private ServiceRegistration<?> signedContentFactoryReg;
+ private ServiceRegistration<?> systemTrustEngineReg;
+ private ServiceRegistration<?> defaultAuthEngineReg;
+ private List<ServiceRegistration<?>> osgiTrustEngineReg;
+ private ServiceRegistration<?> legacyFactoryReg;
+
+ public void initialize(BaseAdaptor adaptor) {
+ SignedBundleHook.ADAPTOR = adaptor;
+ }
+
+ /**
+ * @throws BundleException
+ */
+ public void frameworkStart(BundleContext context) throws BundleException {
+ // check if load time authority is enabled
+ if ((supportSignedBundles & VERIFY_AUTHORITY) != 0) {
+ // install the default bundle install listener
+ installListener = new BundleInstallListener();
+ context.addBundleListener(installListener);
+ // register the default authorization engine
+ Dictionary<String, Object> properties = new Hashtable<String, Object>(7);
+ properties.put(Constants.SERVICE_RANKING, new Integer(Integer.MIN_VALUE));
+ properties.put(SignedContentConstants.AUTHORIZATION_ENGINE, SignedContentConstants.DEFAULT_AUTHORIZATION_ENGINE);
+ defaultAuthEngineReg = context.registerService(AuthorizationEngine.class.getName(), new DefaultAuthorizationEngine(context, ADAPTOR.getState()), properties);
+ }
+
+ // always register the trust engine
+ Dictionary<String, Object> trustEngineProps = new Hashtable<String, Object>(7);
+ trustEngineProps.put(Constants.SERVICE_RANKING, new Integer(Integer.MIN_VALUE));
+ trustEngineProps.put(SignedContentConstants.TRUST_ENGINE, SignedContentConstants.DEFAULT_TRUST_ENGINE);
+ KeyStoreTrustEngine systemTrustEngine = new KeyStoreTrustEngine(CACERTS_PATH, CACERTS_TYPE, null, "System"); //$NON-NLS-1$
+ systemTrustEngineReg = context.registerService(TrustEngine.class.getName(), systemTrustEngine, trustEngineProps);
+ String osgiTrustPath = context.getProperty(OSGI_KEYSTORE);
+ if (osgiTrustPath != null) {
+ try {
+ URL url = new URL(osgiTrustPath);
+ if ("file".equals(url.getProtocol())) { //$NON-NLS-1$
+ trustEngineProps.put(SignedContentConstants.TRUST_ENGINE, OSGI_KEYSTORE);
+ String path = url.getPath();
+ osgiTrustEngineReg = new ArrayList<ServiceRegistration<?>>(1);
+ osgiTrustEngineReg.add(context.registerService(TrustEngine.class.getName(), new KeyStoreTrustEngine(path, CACERTS_TYPE, null, OSGI_KEYSTORE), trustEngineProps));
+ }
+ } catch (MalformedURLException e) {
+ SignedBundleHook.log("Invalid setting for " + OSGI_KEYSTORE, FrameworkLogEntry.WARNING, e); //$NON-NLS-1$
+ }
+ } else {
+ String osgiTrustRepoPaths = context.getProperty(Constants.FRAMEWORK_TRUST_REPOSITORIES);
+ if (osgiTrustRepoPaths != null) {
+ trustEngineProps.put(SignedContentConstants.TRUST_ENGINE, Constants.FRAMEWORK_TRUST_REPOSITORIES);
+ StringTokenizer st = new StringTokenizer(osgiTrustRepoPaths, File.pathSeparator);
+ osgiTrustEngineReg = new ArrayList<ServiceRegistration<?>>(1);
+ while (st.hasMoreTokens()) {
+ String trustRepoPath = st.nextToken();
+ osgiTrustEngineReg.add(context.registerService(TrustEngine.class.getName(), new KeyStoreTrustEngine(trustRepoPath, CACERTS_TYPE, null, OSGI_KEYSTORE), trustEngineProps));
+ }
+ }
+ }
+ if ((supportSignedBundles & VERIFY_TRUST) != 0)
+ // initialize the trust engine listener only if trust is being established with a trust engine
+ trustEngineListener = new TrustEngineListener(context);
+ // always register the signed content factory
+ signedContentFactoryReg = context.registerService(SignedContentFactory.class.getName(), this, null);
+ legacyFactoryReg = context.registerService(CertificateVerifierFactory.class.getName(), new LegacyVerifierFactory(this), null);
+ }
+
+ /**
+ * @throws BundleException
+ */
+ public void frameworkStop(BundleContext context) throws BundleException {
+ if (legacyFactoryReg != null) {
+ legacyFactoryReg.unregister();
+ legacyFactoryReg = null;
+ }
+ if (signedContentFactoryReg != null) {
+ signedContentFactoryReg.unregister();
+ signedContentFactoryReg = null;
+ }
+ if (systemTrustEngineReg != null) {
+ systemTrustEngineReg.unregister();
+ systemTrustEngineReg = null;
+ }
+ if (osgiTrustEngineReg != null) {
+ for (Iterator<ServiceRegistration<?>> it = osgiTrustEngineReg.iterator(); it.hasNext();)
+ it.next().unregister();
+ osgiTrustEngineReg = null;
+ }
+ if (defaultAuthEngineReg != null) {
+ defaultAuthEngineReg.unregister();
+ defaultAuthEngineReg = null;
+ }
+ if (trustEngineListener != null) {
+ trustEngineListener.stopTrustEngineListener();
+ trustEngineListener = null;
+ }
+ if (installListener != null) {
+ context.removeBundleListener(installListener);
+ installListener = null;
+ }
+ if (trustEngineTracker != null) {
+ trustEngineTracker.close();
+ trustEngineTracker = null;
+ }
+ }
+
+ public void frameworkStopping(BundleContext context) {
+ // do nothing
+ }
+
+ public void addProperties(Properties properties) {
+ // do nothing
+ }
+
+ /**
+ * @throws IOException
+ */
+ public URLConnection mapLocationToURLConnection(String location) throws IOException {
+ return null;
+ }
+
+ public void handleRuntimeError(Throwable error) {
+ // do nothing
+ }
+
+ public FrameworkLog createFrameworkLog() {
+ return null;
+ }
+
+ public BundleFile wrapBundleFile(BundleFile bundleFile, Object content, BaseData data, boolean base) {
+ try {
+ if (bundleFile != null) {
+ SignedStorageHook hook = (SignedStorageHook) data.getStorageHook(SignedStorageHook.KEY);
+ SignedBundleFile signedBaseFile;
+ if (base && hook != null) {
+ signedBaseFile = new SignedBundleFile(hook.signedContent, supportSignedBundles);
+ if (hook.signedContent == null) {
+ signedBaseFile.setBundleFile(bundleFile);
+ SignedContentImpl signedContent = signedBaseFile.getSignedContent();
+ hook.signedContent = signedContent != null && signedContent.isSigned() ? signedContent : null;
+ }
+ } else
+ signedBaseFile = new SignedBundleFile(null, supportSignedBundles);
+ signedBaseFile.setBundleFile(bundleFile);
+ SignedContentImpl signedContent = signedBaseFile.getSignedContent();
+ if (signedContent != null && signedContent.isSigned()) {
+ // only use the signed file if there are certs
+ signedContent.setContent(signedBaseFile);
+ bundleFile = signedBaseFile;
+ }
+ }
+ } catch (IOException e) {
+ SignedBundleHook.log("Bad bundle file: " + bundleFile.getBaseFile(), FrameworkLogEntry.WARNING, e); //$NON-NLS-1$
+ } catch (GeneralSecurityException e) {
+ SignedBundleHook.log("Bad bundle file: " + bundleFile.getBaseFile(), FrameworkLogEntry.WARNING, e); //$NON-NLS-1$
+ }
+ return bundleFile;
+ }
+
+ public void addHooks(HookRegistry hookRegistry) {
+ hookRegistry.addAdaptorHook(this);
+ String[] support = ManifestElement.getArrayFromList(FrameworkProperties.getProperty(SIGNED_CONTENT_SUPPORT, FrameworkProperties.getProperty(SIGNED_BUNDLE_SUPPORT)), ","); //$NON-NLS-1$
+ for (int i = 0; i < support.length; i++) {
+ if (SUPPORT_CERTIFICATE.equals(support[i]))
+ supportSignedBundles |= VERIFY_CERTIFICATE;
+ else if (SUPPORT_TRUST.equals(support[i]))
+ supportSignedBundles |= VERIFY_CERTIFICATE | VERIFY_TRUST;
+ else if (SUPPORT_RUNTIME.equals(support[i]))
+ supportSignedBundles |= VERIFY_CERTIFICATE | VERIFY_RUNTIME;
+ else if (SUPPORT_AUTHORITY.equals(support[i]))
+ supportSignedBundles |= VERIFY_CERTIFICATE | VERIFY_TRUST | VERIFY_AUTHORITY;
+ else if (SUPPORT_TRUE.equals(support[i]) || SUPPORT_ALL.equals(support[i]))
+ supportSignedBundles |= VERIFY_ALL;
+ }
+ if ((supportSignedBundles & VERIFY_CERTIFICATE) != 0) {
+ hookRegistry.addStorageHook(new SignedStorageHook());
+ hookRegistry.addBundleFileWrapperFactoryHook(this);
+ }
+ }
+
+ public SignedContent getSignedContent(File content) throws IOException, InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException {
+ if (content == null)
+ throw new IllegalArgumentException("null content"); //$NON-NLS-1$
+ BundleFile contentBundleFile;
+ if (content.isDirectory())
+ contentBundleFile = new DirBundleFile(content);
+ else
+ contentBundleFile = new ZipBundleFile(content, null);
+ SignedBundleFile result = new SignedBundleFile(null, VERIFY_ALL);
+ try {
+ result.setBundleFile(contentBundleFile);
+ } catch (InvalidKeyException e) {
+ throw (InvalidKeyException) new InvalidKeyException(NLS.bind(SignedContentMessages.Factory_SignedContent_Error, content)).initCause(e);
+ } catch (SignatureException e) {
+ throw (SignatureException) new SignatureException(NLS.bind(SignedContentMessages.Factory_SignedContent_Error, content)).initCause(e);
+ } catch (CertificateException e) {
+ throw (CertificateException) new CertificateException(NLS.bind(SignedContentMessages.Factory_SignedContent_Error, content)).initCause(e);
+ } catch (NoSuchAlgorithmException e) {
+ throw (NoSuchAlgorithmException) new NoSuchAlgorithmException(NLS.bind(SignedContentMessages.Factory_SignedContent_Error, content)).initCause(e);
+ } catch (NoSuchProviderException e) {
+ throw (NoSuchProviderException) new NoSuchProviderException(NLS.bind(SignedContentMessages.Factory_SignedContent_Error, content)).initCause(e);
+ }
+ return new SignedContentFile(result.getSignedContent());
+ }
+
+ public SignedContent getSignedContent(Bundle bundle) throws IOException, InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException, IllegalArgumentException {
+ final BundleData data = ((AbstractBundle) bundle).getBundleData();
+ if (!(data instanceof BaseData))
+ throw new IllegalArgumentException("Invalid bundle object. No BaseData found."); //$NON-NLS-1$
+ SignedStorageHook hook = (SignedStorageHook) ((BaseData) data).getStorageHook(SignedStorageHook.KEY);
+ SignedContent result = hook != null ? hook.signedContent : null;
+ if (result != null)
+ return result; // just reuse the signed content the storage hook
+ // must create a new signed content using the raw file
+ if (System.getSecurityManager() == null)
+ return getSignedContent(((BaseData) data).getBundleFile().getBaseFile());
+ try {
+ return AccessController.doPrivileged(new PrivilegedExceptionAction<SignedContent>() {
+ public SignedContent run() throws Exception {
+ return getSignedContent(((BaseData) data).getBundleFile().getBaseFile());
+ }
+ });
+ } catch (PrivilegedActionException e) {
+ if (e.getException() instanceof IOException)
+ throw (IOException) e.getException();
+ if (e.getException() instanceof InvalidKeyException)
+ throw (InvalidKeyException) e.getException();
+ if (e.getException() instanceof SignatureException)
+ throw (SignatureException) e.getException();
+ if (e.getException() instanceof CertificateException)
+ throw (CertificateException) e.getException();
+ if (e.getException() instanceof NoSuchAlgorithmException)
+ throw (NoSuchAlgorithmException) e.getException();
+ if (e.getException() instanceof NoSuchProviderException)
+ throw (NoSuchProviderException) e.getException();
+ throw new RuntimeException("Unknown error.", e.getException()); //$NON-NLS-1$
+ }
+ }
+
+ public static void log(String msg, int severity, Throwable t) {
+ if (SignedBundleHook.ADAPTOR == null) {
+ System.err.println(msg);
+ t.printStackTrace();
+ return;
+ }
+ FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, severity, 0, msg, 0, t, null);
+ SignedBundleHook.ADAPTOR.getFrameworkLog().log(entry);
+ }
+
+ static BundleContext getContext() {
+ if (ADAPTOR == null)
+ return null;
+ return ADAPTOR.getContext();
+ }
+
+ static TrustEngine[] getTrustEngines() {
+ // find all the trust engines available
+ BundleContext context = SignedBundleHook.getContext();
+ if (context == null)
+ return new TrustEngine[0];
+ if (trustEngineTracker == null) {
+ // read the trust provider security property
+ String trustEngineProp = FrameworkProperties.getProperty(SignedContentConstants.TRUST_ENGINE);
+ Filter filter = null;
+ if (trustEngineProp != null)
+ try {
+ filter = FilterImpl.newInstance("(&(" + Constants.OBJECTCLASS + "=" + TrustEngine.class.getName() + ")(" + SignedContentConstants.TRUST_ENGINE + "=" + trustEngineProp + "))"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$//$NON-NLS-5$
+ } catch (InvalidSyntaxException e) {
+ SignedBundleHook.log("Invalid trust engine filter", FrameworkLogEntry.WARNING, e); //$NON-NLS-1$
+ }
+ if (filter != null) {
+ trustEngineTracker = new ServiceTracker<TrustEngine, TrustEngine>(context, filter, null);
+ } else
+ trustEngineTracker = new ServiceTracker<TrustEngine, TrustEngine>(context, TrustEngine.class.getName(), null);
+ trustEngineTracker.open();
+ }
+ Object[] services = trustEngineTracker.getServices();
+ if (services != null) {
+ TrustEngine[] engines = new TrustEngine[services.length];
+ System.arraycopy(services, 0, engines, 0, services.length);
+ return engines;
+ }
+ return new TrustEngine[0];
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentConstants.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentConstants.java
new file mode 100644
index 000000000..115621a91
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentConstants.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 IBM Corporation and others. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors: IBM Corporation - initial API and implementation
+ ******************************************************************************/
+
+package org.eclipse.osgi.internal.signedcontent;
+
+public interface SignedContentConstants {
+
+ public static final String SHA1_STR = "SHA1"; //$NON-NLS-1$
+ public static final String SHA256_STR = "SHA256"; //$NON-NLS-1$
+ public static final String SHA384_STR = "SHA384"; //$NON-NLS-1$
+ public static final String SHA512_STR = "SHA512"; //$NON-NLS-1$
+ public static final String SHA224_STR = "SHA224"; //$NON-NLS-1$
+ public static final String SHA512_224_STR = "SHA512-224"; //$NON-NLS-1$
+ public static final String SHA512_256_STR = "SHA512-256"; //$NON-NLS-1$
+ public static final String MD5_STR = "MD5"; //$NON-NLS-1$
+ public static final String MD2_STR = "MD2"; //$NON-NLS-1$
+
+ public static final String DOT_DSA = ".DSA"; //$NON-NLS-1$
+ public static final String DOT_RSA = ".RSA"; //$NON-NLS-1$
+ public static final String DOT_SF = ".SF"; //$NON-NLS-1$
+ public static final String SIG_DASH = "SIG-"; //$NON-NLS-1$
+ public static final String META_INF = "META-INF/"; //$NON-NLS-1$
+ public static final String META_INF_MANIFEST_MF = "META-INF/MANIFEST.MF"; //$NON-NLS-1$
+ public static final String[] EMPTY_STRING = new String[0];
+
+ //
+ // following are variables and methods to cache the entries related data
+ // for a given MF file
+ //
+ public static final String MF_ENTRY_NEWLN_NAME = "\nName: "; //$NON-NLS-1$
+ public static final String MF_ENTRY_NAME = "Name: "; //$NON-NLS-1$
+ public static final String MF_DIGEST_PART = "-Digest: "; //$NON-NLS-1$
+ public static final String digestManifestSearch = "-Digest-Manifest: "; //$NON-NLS-1$
+ public static final int digestManifestSearchLen = digestManifestSearch.length();
+
+ public static final int SIGNEDDATA_OID[] = {1, 2, 840, 113549, 1, 7, 2};
+ public static final int MD5_OID[] = {1, 2, 840, 113549, 2, 5};
+ public static final int MD2_OID[] = {1, 2, 840, 113549, 2, 2};
+
+ public static final int SHA1_OID[] = {1, 3, 14, 3, 2, 26};
+
+ public static final int SHA256_OID[] = {2, 16, 840, 1, 101, 3, 4, 2, 1};
+ public static final int SHA384_OID[] = {2, 16, 840, 1, 101, 3, 4, 2, 2};
+ public static final int SHA512_OID[] = {2, 16, 840, 1, 101, 3, 4, 2, 3};
+ public static final int SHA224_OID[] = {2, 16, 840, 1, 101, 3, 4, 2, 4};
+ public static final int SHA512_224_OID[] = {2, 16, 840, 1, 101, 3, 4, 2, 5};
+ public static final int SHA512_256_OID[] = {2, 16, 840, 1, 101, 3, 4, 2, 6};
+
+ public static final int DSA_OID[] = {1, 2, 840, 10040, 4, 1};
+ public static final int RSA_OID[] = {1, 2, 840, 113549, 1, 1, 1};
+
+ // constant for trust engine service
+ public static final String TRUST_ENGINE = "osgi.signedcontent.trust.engine"; //$NON-NLS-1$
+ public static final Object DEFAULT_TRUST_ENGINE = "org.eclipse.osgi"; //$NON-NLS-1$
+
+ // constants for authorization engine service
+ public static final String AUTHORIZATION_ENGINE = "osgi.signedcontent.authorization.engine"; //$NON-NLS-1$
+ public static final Object DEFAULT_AUTHORIZATION_ENGINE = "org.eclipse.osgi"; //$NON-NLS-1$
+
+ // constant for the timestamp related
+ public static final int TIMESTAMP_OID[] = {1, 2, 840, 113549, 1, 9, 16, 2, 14};
+ public static final int TIMESTAMP_TST_OID[] = {1, 2, 840, 113549, 1, 9, 16, 1, 4};
+ public static final int SIGNING_TIME[] = {1, 2, 840, 113549, 1, 9, 5};
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentFile.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentFile.java
new file mode 100644
index 000000000..fdf572cf7
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentFile.java
@@ -0,0 +1,131 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.internal.signedcontent;
+
+import java.io.IOException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.util.*;
+import org.eclipse.osgi.signedcontent.*;
+
+/*
+ * This class is used by the SignedContentFactory to create SignedContent objects from File objects. This is needed
+ * to avoid leaving the underlying ZipFiles open for the SignedContent objects returned from the
+ * SignedContentFactory (bug 225090)
+ */
+public class SignedContentFile implements SignedContent {
+
+ private final SignedContentImpl signedContent;
+ // a cache of verification exceptions
+ private Map<String, Throwable> entryExceptions = null;
+
+ public SignedContentFile(SignedContentImpl signedContent) {
+ try {
+ signedContent.content.close();
+ } catch (IOException e) {
+ // do nothing
+ }
+ this.signedContent = signedContent;
+ }
+
+ public void checkValidity(SignerInfo signerInfo) throws CertificateExpiredException, CertificateNotYetValidException {
+ signedContent.checkValidity(signerInfo);
+ }
+
+ public synchronized SignedContentEntry[] getSignedEntries() {
+ SignedContentEntry[] entries = signedContent.getSignedEntries();
+ if (signedContent == null)
+ return null;
+ SignedContentEntry[] results = new SignedContentEntry[entries.length];
+ Map<String, Throwable> exceptions = getEntryExceptions(true);
+ for (int i = 0; i < entries.length; i++) {
+ try {
+ entries[i].verify();
+ } catch (Throwable t) {
+ exceptions.put(entries[i].getName(), t);
+ }
+ results[i] = new SignedContentFileEntry(entries[i]);
+ }
+ try {
+ // ensure the content is closed after caching the exceptions
+ signedContent.content.close();
+ } catch (IOException e) {
+ // do nothing
+ }
+ return results;
+ }
+
+ public synchronized SignedContentEntry getSignedEntry(String name) {
+ if (getEntryExceptions(false) == null)
+ getSignedEntries(); // populate the entry exceptions
+ SignedContentEntry entry = signedContent.getSignedEntry(name);
+ return entry == null ? null : new SignedContentFileEntry(entry);
+ }
+
+ public SignerInfo[] getSignerInfos() {
+ return signedContent.getSignerInfos();
+ }
+
+ public Date getSigningTime(SignerInfo signerInfo) {
+ return signedContent.getSigningTime(signerInfo);
+ }
+
+ public SignerInfo getTSASignerInfo(SignerInfo signerInfo) {
+ return signedContent.getTSASignerInfo(signerInfo);
+ }
+
+ public boolean isSigned() {
+ return signedContent.isSigned();
+ }
+
+ synchronized Map<String, Throwable> getEntryExceptions(boolean create) {
+ if (create && entryExceptions == null)
+ entryExceptions = new HashMap<String, Throwable>(5);
+ return entryExceptions;
+ }
+
+ public class SignedContentFileEntry implements SignedContentEntry {
+ private final SignedContentEntry entry;
+
+ public SignedContentFileEntry(SignedContentEntry entry) {
+ this.entry = entry;
+ }
+
+ public String getName() {
+ return entry.getName();
+ }
+
+ public SignerInfo[] getSignerInfos() {
+ return entry.getSignerInfos();
+ }
+
+ public boolean isSigned() {
+ return entry.isSigned();
+ }
+
+ public void verify() throws IOException, InvalidContentException {
+ // check the entry exceptions map for the entry name
+ Map<String, Throwable> exceptions = getEntryExceptions(false);
+ Throwable t = exceptions == null ? null : (Throwable) exceptions.get(entry.getName());
+ if (t == null)
+ return;
+ if (t instanceof IOException)
+ throw (IOException) t;
+ if (t instanceof InvalidContentException)
+ throw (InvalidContentException) t;
+ if (t instanceof Error)
+ throw (Error) t;
+ if (t instanceof RuntimeException)
+ throw (RuntimeException) t;
+ }
+
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentImpl.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentImpl.java
new file mode 100644
index 000000000..af03ea893
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentImpl.java
@@ -0,0 +1,168 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 IBM Corporation and others. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors: IBM Corporation - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.osgi.internal.signedcontent;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.cert.*;
+import java.util.*;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry;
+import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile;
+import org.eclipse.osgi.signedcontent.*;
+import org.eclipse.osgi.util.NLS;
+
+public class SignedContentImpl implements SignedContent {
+ final static SignerInfo[] EMPTY_SIGNERINFO = new SignerInfo[0];
+ // the content which is signed
+ volatile SignedBundleFile content; // TODO can this be more general?
+ // the content entry md results used for entry content verification
+ // keyed by entry path -> {SignerInfo[] infos, byte[][] results)}
+ private final Map<String, Object> contentMDResults;
+ private final SignerInfo[] signerInfos;
+ // map of tsa singers keyed by SignerInfo -> {tsa_SignerInfo, signingTime}
+ private Map<SignerInfo, Object[]> tsaSignerInfos;
+ volatile private boolean checkedValid = false;
+
+ public SignedContentImpl(SignerInfo[] signerInfos, Map<String, Object> contentMDResults) {
+ this.signerInfos = signerInfos == null ? EMPTY_SIGNERINFO : signerInfos;
+ this.contentMDResults = contentMDResults;
+ }
+
+ public SignedContentEntry[] getSignedEntries() {
+ if (contentMDResults == null)
+ return new SignedContentEntry[0];
+ List<SignedContentEntry> results = new ArrayList<SignedContentEntry>(contentMDResults.size());
+ for (Map.Entry<String, Object> entry : contentMDResults.entrySet()) {
+ String entryName = entry.getKey();
+ Object[] mdResult = (Object[]) entry.getValue();
+ results.add(new SignedContentEntryImpl(entryName, (SignerInfo[]) mdResult[0]));
+ }
+ return results.toArray(new SignedContentEntry[results.size()]);
+ }
+
+ public SignedContentEntry getSignedEntry(String name) {
+ if (contentMDResults == null)
+ return null;
+ Object[] mdResult = (Object[]) contentMDResults.get(name);
+ return mdResult == null ? null : new SignedContentEntryImpl(name, (SignerInfo[]) mdResult[0]);
+ }
+
+ public SignerInfo[] getSignerInfos() {
+ return signerInfos;
+ }
+
+ public Date getSigningTime(SignerInfo signerInfo) {
+ if (tsaSignerInfos == null)
+ return null;
+ Object[] tsaInfo = tsaSignerInfos.get(signerInfo);
+ return tsaInfo == null ? null : (Date) tsaInfo[1];
+ }
+
+ public SignerInfo getTSASignerInfo(SignerInfo signerInfo) {
+ if (tsaSignerInfos == null)
+ return null;
+ Object[] tsaInfo = tsaSignerInfos.get(signerInfo);
+ return tsaInfo == null ? null : (SignerInfo) tsaInfo[0];
+ }
+
+ public boolean isSigned() {
+ return signerInfos.length > 0;
+ }
+
+ public void checkValidity(SignerInfo signer) throws CertificateExpiredException, CertificateNotYetValidException {
+ Date signingTime = getSigningTime(signer);
+ if (checkedValid)
+ return;
+ Certificate[] certs = signer.getCertificateChain();
+ for (int i = 0; i < certs.length; i++) {
+ if (!(certs[i] instanceof X509Certificate))
+ continue;
+ if (signingTime == null)
+ ((X509Certificate) certs[i]).checkValidity();
+ else
+ ((X509Certificate) certs[i]).checkValidity(signingTime);
+ }
+ checkedValid = true;
+ }
+
+ void setContent(SignedBundleFile content) {
+ this.content = content;
+ }
+
+ void setTSASignerInfos(Map<SignerInfo, Object[]> tsaSignerInfos) {
+ this.tsaSignerInfos = tsaSignerInfos;
+ }
+
+ void addTSASignerInfo(SignerInfo baseInfo, SignerInfo tsaSignerInfo, Date signingTime) {
+ // sanity check to make sure the baseInfo is here
+ if (!containsInfo(baseInfo))
+ throw new IllegalArgumentException("The baseInfo is not found"); //$NON-NLS-1$
+ if (tsaSignerInfos == null)
+ tsaSignerInfos = new HashMap<SignerInfo, Object[]>(signerInfos.length);
+ tsaSignerInfos.put(baseInfo, new Object[] {tsaSignerInfo, signingTime});
+ }
+
+ Map<String, Object> getContentMDResults() {
+ return contentMDResults;
+ }
+
+ private boolean containsInfo(SignerInfo signerInfo) {
+ for (int i = 0; i < signerInfos.length; i++)
+ if (signerInfo == signerInfos[i])
+ return true;
+ return false;
+ }
+
+ InputStream getDigestInputStream(BundleEntry nestedEntry) throws IOException {
+ if (contentMDResults == null)
+ return nestedEntry.getInputStream();
+ Object[] mdResult = (Object[]) contentMDResults.get(nestedEntry.getName());
+ if (mdResult == null)
+ return null;
+ return new DigestedInputStream(nestedEntry, content, (SignerInfo[]) mdResult[0], (byte[][]) mdResult[1], nestedEntry.getSize());
+ }
+
+ public class SignedContentEntryImpl implements SignedContentEntry {
+ private final String entryName;
+ private final SignerInfo[] entrySigners;
+
+ public SignedContentEntryImpl(String entryName, SignerInfo[] entrySigners) {
+ this.entryName = entryName;
+ this.entrySigners = entrySigners == null ? EMPTY_SIGNERINFO : entrySigners;
+ }
+
+ public String getName() {
+ return entryName;
+ }
+
+ public SignerInfo[] getSignerInfos() {
+ return entrySigners;
+ }
+
+ public boolean isSigned() {
+ return entrySigners.length > 0;
+ }
+
+ public void verify() throws IOException, InvalidContentException {
+ BundleFile currentContent = content;
+ if (currentContent == null)
+ throw new InvalidContentException("The content was not set", null); //$NON-NLS-1$
+ BundleEntry entry = null;
+ SecurityException exception = null;
+ try {
+ entry = currentContent.getEntry(entryName);
+ } catch (SecurityException e) {
+ exception = e;
+ }
+ if (entry == null)
+ throw new InvalidContentException(NLS.bind(SignedContentMessages.file_is_removed_from_jar, entryName, currentContent.getBaseFile().toString()), exception);
+ entry.getBytes();
+ }
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentMessages.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentMessages.java
new file mode 100644
index 000000000..28cab184d
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentMessages.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.osgi.internal.signedcontent;
+
+import org.eclipse.osgi.util.NLS;
+
+public class SignedContentMessages extends NLS {
+
+ // Jar file is tampered
+ public static String file_is_removed_from_jar;
+ public static String File_In_Jar_Is_Tampered;
+ public static String Security_File_Is_Tampered;
+ public static String Signature_Not_Verify;
+
+ // Jar file parsing
+ public static String SF_File_Parsing_Error;
+
+ // PKCS7 parsing errors
+ public static String PKCS7_SignerInfo_Version_Not_Supported;
+ public static String PKCS7_Invalid_File;
+ public static String PKCS7_Parse_Signing_Time;
+
+ // Security Exceptions
+ public static String Algorithm_Not_Supported;
+
+ public static String Factory_SignedContent_Error;
+
+ public static String Default_Trust_Keystore_Load_Failed;
+ public static String Default_Trust_Read_Only;
+ public static String Default_Trust_Cert_Not_Found;
+ public static String Default_Trust_Existing_Cert;
+ public static String Default_Trust_Existing_Alias;
+
+ // private static final String BUNDLE_PACKAGE = SignedContentMessages.class.getPackage().getName() + ".";
+ private static final String BUNDLE_PACKAGE = "org.eclipse.osgi.internal.signedcontent."; //$NON-NLS-1$
+ private static final String BUNDLE_FILENAME = "SignedContentMessages"; //$NON-NLS-1$
+ private static final String BUNDLE_NAME = BUNDLE_PACKAGE + BUNDLE_FILENAME;
+
+ static {
+ NLS.initializeMessages(BUNDLE_NAME, SignedContentMessages.class);
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentMessages.properties b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentMessages.properties
new file mode 100644
index 000000000..47dace892
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedContentMessages.properties
@@ -0,0 +1,37 @@
+###############################################################################
+# Copyright (c) 2006, 2010 IBM Corporation and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Contributors:
+# IBM Corporation - initial API and implementation
+###############################################################################
+
+# Jar file tampered error messages
+file_is_removed_from_jar = A file \"{0}\" has been removed from the jar: {1}
+File_In_Jar_Is_Tampered = The file \"{0}\" in the jar \"{1}\" has been tampered!
+Security_File_Is_Tampered = Either the manifest file or the signature file has been tampered in this jar: {0}
+Signature_Not_Verify = The signature cannot be verified for the signer \"{0}\" in this jar: {1}
+
+# Jar file parsing
+SF_File_Parsing_Error = Error occurs parsing the .SF file to find out the digest algorithm in this bundle: {0}
+
+# PKCS7 parsing errors
+PKCS7_SignerInfo_Version_Not_Supported = Only PKCS7 SignerInfos with a version of 1 are supported.
+PKCS7_Invalid_File = The file \"{0}\" is not a valid PKCS7 file in the jar: {1}
+PKCS7_Parse_Signing_Time = The time stamp in the pkcs7 file cannot be parsed properly!
+
+# Security Exceptions
+Algorithm_Not_Supported = {0} digest algorithm is not supported!
+
+# SignedContentFactory exception
+Factory_SignedContent_Error = An error occurred while processing the signatures for the file: {0}
+
+# Default Trust Engine
+Default_Trust_Keystore_Load_Failed = Failed to load the keystore from: {0}
+Default_Trust_Read_Only=This trust engine is read only.
+Default_Trust_Cert_Not_Found = Certificate not found.
+Default_Trust_Existing_Cert = Certificate already present in store.
+Default_Trust_Existing_Alias = A certificate is already present for this alias. \ No newline at end of file
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedStorageHook.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedStorageHook.java
new file mode 100644
index 000000000..2fe55e59c
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignedStorageHook.java
@@ -0,0 +1,243 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2010 IBM Corporation and others. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors: IBM Corporation - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.osgi.internal.signedcontent;
+
+import java.io.*;
+import java.security.cert.*;
+import java.util.*;
+import org.eclipse.osgi.baseadaptor.BaseData;
+import org.eclipse.osgi.baseadaptor.hooks.StorageHook;
+import org.eclipse.osgi.framework.util.KeyedElement;
+import org.eclipse.osgi.signedcontent.SignedContent;
+import org.eclipse.osgi.signedcontent.SignerInfo;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+
+public class SignedStorageHook implements StorageHook {
+ public static final String KEY = SignedStorageHook.class.getName();
+ public static final int HASHCODE = KEY.hashCode();
+ private static final int STORAGE_VERSION = 3;
+ private static List<SignerInfo> savedSignerInfo = new ArrayList<SignerInfo>(5);
+ private static long firstIDSaved = -1;
+ private static long lastIDSaved = -1;
+ private static List<SignerInfo> loadedSignerInfo = new ArrayList<SignerInfo>(5);
+ private static long lastIDLoaded;
+
+ private BaseData bundledata;
+ SignedContentImpl signedContent;
+
+ public int getStorageVersion() {
+ return STORAGE_VERSION;
+ }
+
+ /**
+ * @throws BundleException
+ */
+ public StorageHook create(BaseData data) throws BundleException {
+ SignedStorageHook hook = new SignedStorageHook();
+ hook.bundledata = data;
+ return hook;
+ }
+
+ /**
+ * @throws BundleException
+ */
+ public void initialize(Dictionary<String, String> manifest) throws BundleException {
+ // do nothing
+ }
+
+ public StorageHook load(BaseData target, DataInputStream is) throws IOException {
+ if (lastIDLoaded > target.getBundleID())
+ loadedSignerInfo.clear();
+ lastIDLoaded = target.getBundleID();
+ SignedStorageHook hook = new SignedStorageHook();
+ hook.bundledata = target;
+ boolean signed = is.readBoolean();
+ if (!signed)
+ return hook;
+ int numSigners = is.readInt();
+ SignerInfo[] signerInfos = new SignerInfo[numSigners];
+ for (int i = 0; i < numSigners; i++)
+ signerInfos[i] = readSignerInfo(is);
+
+ int resultsSize = is.readInt();
+ Map<String, Object> contentMDResults = null;
+ if (resultsSize > 0) {
+ contentMDResults = new HashMap<String, Object>(resultsSize);
+ for (int i = 0; i < resultsSize; i++) {
+ String path = is.readUTF();
+ int numEntrySigners = is.readInt();
+ SignerInfo[] entrySigners = new SignerInfo[numEntrySigners];
+ byte[][] entryResults = new byte[numEntrySigners][];
+ for (int j = 0; j < numEntrySigners; j++) {
+ entrySigners[j] = readSignerInfo(is);
+ int resultSize = is.readInt();
+ entryResults[j] = new byte[resultSize];
+ is.readFully(entryResults[j]);
+ }
+ contentMDResults.put(path, new Object[] {entrySigners, entryResults});
+ }
+ }
+ SignedContentImpl result = new SignedContentImpl(signerInfos, contentMDResults);
+ for (int i = 0; i < numSigners; i++) {
+ boolean hasTSA = is.readBoolean();
+ if (!hasTSA)
+ continue;
+ SignerInfo tsaSigner = readSignerInfo(is);
+ Date signingDate = new Date(is.readLong());
+ result.addTSASignerInfo(signerInfos[i], tsaSigner, signingDate);
+ }
+ hook.signedContent = result;
+ return hook;
+ }
+
+ public void save(DataOutputStream os) throws IOException {
+ getFirstLastID();
+ if (firstIDSaved == bundledata.getBundleID())
+ savedSignerInfo.clear();
+ if (lastIDSaved == bundledata.getBundleID())
+ firstIDSaved = lastIDSaved = -1;
+ os.writeBoolean(signedContent != null);
+ if (signedContent == null)
+ return;
+ SignerInfo[] signerInfos = signedContent.getSignerInfos();
+ os.writeInt(signerInfos.length);
+ for (int i = 0; i < signerInfos.length; i++)
+ saveSignerInfo(signerInfos[i], os);
+
+ // keyed by entry path -> {SignerInfo[] infos, byte[][] results)}
+ Map<String, Object> contentMDResults = signedContent.getContentMDResults();
+ os.writeInt(contentMDResults == null ? -1 : contentMDResults.size());
+ if (contentMDResults != null)
+ for (Map.Entry<String, Object> entry : contentMDResults.entrySet()) {
+ String path = entry.getKey();
+ os.writeUTF(path);
+ Object[] signerResults = (Object[]) entry.getValue();
+ SignerInfo[] entrySigners = (SignerInfo[]) signerResults[0];
+ byte[][] entryResults = (byte[][]) signerResults[1];
+ os.writeInt(entrySigners.length);
+ for (int i = 0; i < entrySigners.length; i++) {
+ saveSignerInfo(entrySigners[i], os);
+ os.writeInt(entryResults[i].length);
+ os.write(entryResults[i]);
+ }
+ }
+
+ for (int i = 0; i < signerInfos.length; i++) {
+ SignerInfo tsaInfo = signedContent.getTSASignerInfo(signerInfos[i]);
+ os.writeBoolean(tsaInfo != null);
+ if (tsaInfo == null)
+ continue;
+ saveSignerInfo(tsaInfo, os);
+ Date signingTime = signedContent.getSigningTime(signerInfos[i]);
+ os.writeLong(signingTime != null ? signingTime.getTime() : Long.MIN_VALUE);
+ }
+ }
+
+ private void saveSignerInfo(SignerInfo signerInfo, DataOutputStream os) throws IOException {
+ int cacheIdx = savedSignerInfo.indexOf(signerInfo);
+ os.writeInt(cacheIdx);
+ if (cacheIdx >= 0)
+ return;
+ Certificate[] certs = signerInfo.getCertificateChain();
+ int anchorIndex = -1;
+ os.writeInt(certs == null ? 0 : certs.length);
+ if (certs != null)
+ for (int i = 0; i < certs.length; i++) {
+ if (certs[i].equals(signerInfo.getTrustAnchor()))
+ anchorIndex = i;
+ byte[] certBytes;
+ try {
+ certBytes = certs[i].getEncoded();
+ } catch (CertificateEncodingException e) {
+ throw (IOException) new IOException(e.getMessage()).initCause(e);
+ }
+ os.writeInt(certBytes.length);
+ os.write(certBytes);
+ }
+ os.writeInt(anchorIndex);
+ os.writeUTF(signerInfo.getMessageDigestAlgorithm());
+ savedSignerInfo.add(signerInfo);
+ }
+
+ private SignerInfo readSignerInfo(DataInputStream is) throws IOException {
+ int index = is.readInt();
+ if (index >= 0)
+ return loadedSignerInfo.get(index);
+ int numCerts = is.readInt();
+ Certificate[] certs = new Certificate[numCerts];
+ for (int i = 0; i < numCerts; i++) {
+ int certSize = is.readInt();
+ byte[] certBytes = new byte[certSize];
+ is.readFully(certBytes);
+ try {
+ certs[i] = PKCS7Processor.certFact.generateCertificate(new ByteArrayInputStream(certBytes));
+ } catch (CertificateException e) {
+ throw (IOException) new IOException(e.getMessage()).initCause(e);
+ }
+ }
+ int anchorIdx = is.readInt();
+ SignerInfoImpl result = new SignerInfoImpl(certs, anchorIdx >= 0 ? certs[anchorIdx] : null, is.readUTF());
+ loadedSignerInfo.add(result);
+ return result;
+ }
+
+ private void getFirstLastID() {
+ if (firstIDSaved >= 0)
+ return;
+ Bundle[] bundles = bundledata.getAdaptor().getContext().getBundles();
+ if (bundles.length > 1) {
+ firstIDSaved = bundles[1].getBundleId();
+ lastIDSaved = bundles[bundles.length - 1].getBundleId();
+ }
+ }
+
+ public void copy(StorageHook storageHook) {
+ // do nothing
+ }
+
+ public void validate() throws IllegalArgumentException {
+ // do nothing
+ }
+
+ /**
+ * @throws BundleException
+ */
+ public Dictionary<String, String> getManifest(boolean firstLoad) throws BundleException {
+ // do nothing
+ return null;
+ }
+
+ public boolean forgetStatusChange(int status) {
+ // do nothing
+ return false;
+ }
+
+ public boolean forgetStartLevelChange(int startlevel) {
+ // do nothing
+ return false;
+ }
+
+ public int getKeyHashCode() {
+ return HASHCODE;
+ }
+
+ public boolean compare(KeyedElement other) {
+ return other.getKey() == KEY;
+ }
+
+ public Object getKey() {
+ return KEY;
+ }
+
+ public SignedContent getSignedContent() {
+ return signedContent;
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignerInfoImpl.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignerInfoImpl.java
new file mode 100644
index 000000000..84673b14e
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/SignerInfoImpl.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and others. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors: IBM Corporation - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.osgi.internal.signedcontent;
+
+import java.security.cert.Certificate;
+import org.eclipse.osgi.signedcontent.SignerInfo;
+
+public class SignerInfoImpl implements SignerInfo {
+ private final Certificate[] chain;
+ private final String mdAlgorithm;
+ volatile private Certificate trustAnchor;
+
+ public SignerInfoImpl(Certificate[] chain, Certificate trustAnchor, String mdAlgorithm) {
+ this.chain = chain;
+ this.trustAnchor = trustAnchor;
+ this.mdAlgorithm = mdAlgorithm;
+ }
+
+ public Certificate[] getCertificateChain() {
+ return chain;
+ }
+
+ public Certificate getTrustAnchor() {
+ return trustAnchor;
+ }
+
+ public boolean isTrusted() {
+ return trustAnchor != null;
+ }
+
+ void setTrustAnchor(Certificate trustAnchor) {
+ this.trustAnchor = trustAnchor;
+ }
+
+ public String getMessageDigestAlgorithm() {
+ return mdAlgorithm;
+ }
+
+ public int hashCode() {
+ int result = mdAlgorithm.hashCode();
+ for (int i = 0; i < chain.length; i++)
+ result += chain[i].hashCode();
+ // Note that we do not hash based on trustAnchor;
+ // this changes dynamically but we need a constant hashCode for purposes of
+ // hashing in a Set.
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (!(obj instanceof SignerInfo))
+ return false;
+ if (obj == this)
+ return true;
+ SignerInfo other = (SignerInfo) obj;
+ if (!mdAlgorithm.equals(other.getMessageDigestAlgorithm()))
+ return false;
+ Certificate[] otherCerts = other.getCertificateChain();
+ if (otherCerts.length != chain.length)
+ return false;
+ for (int i = 0; i < chain.length; i++)
+ if (!chain[i].equals(otherCerts[i]))
+ return false;
+ return trustAnchor == null ? other.getTrustAnchor() == null : trustAnchor.equals(other.getTrustAnchor());
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/TrustEngineListener.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/TrustEngineListener.java
new file mode 100644
index 000000000..e965a401f
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/signedcontent/TrustEngineListener.java
@@ -0,0 +1,167 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 IBM Corporation and others. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors: IBM Corporation - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.osgi.internal.signedcontent;
+
+import java.security.cert.Certificate;
+import java.util.*;
+import org.eclipse.osgi.baseadaptor.BaseData;
+import org.eclipse.osgi.framework.internal.core.*;
+import org.eclipse.osgi.framework.log.FrameworkLogEntry;
+import org.eclipse.osgi.internal.provisional.service.security.AuthorizationEngine;
+import org.eclipse.osgi.signedcontent.SignerInfo;
+import org.osgi.framework.*;
+import org.osgi.framework.Constants;
+import org.osgi.service.packageadmin.PackageAdmin;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class TrustEngineListener {
+ // this is a singleton listener; see SignedBundleHook for initialization
+ private volatile static TrustEngineListener instance;
+ private final BundleContext context;
+ private final ServiceTracker<AuthorizationEngine, AuthorizationEngine> authorizationTracker;
+
+ TrustEngineListener(BundleContext context) {
+ this.context = context;
+ // read the trust provider security property
+ String authEngineProp = FrameworkProperties.getProperty(SignedContentConstants.AUTHORIZATION_ENGINE);
+ Filter filter = null;
+ if (authEngineProp != null)
+ try {
+ filter = FilterImpl.newInstance("(&(" + Constants.OBJECTCLASS + "=" + AuthorizationEngine.class.getName() + ")(" + SignedContentConstants.AUTHORIZATION_ENGINE + "=" + authEngineProp + "))"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$//$NON-NLS-5$
+ } catch (InvalidSyntaxException e) {
+ SignedBundleHook.log("Invalid authorization filter", FrameworkLogEntry.WARNING, e); //$NON-NLS-1$
+ }
+ if (filter != null)
+ authorizationTracker = new ServiceTracker<AuthorizationEngine, AuthorizationEngine>(context, filter, null);
+ else
+ authorizationTracker = new ServiceTracker<AuthorizationEngine, AuthorizationEngine>(context, AuthorizationEngine.class.getName(), null);
+ authorizationTracker.open();
+ instance = this;
+ }
+
+ public static TrustEngineListener getInstance() {
+ return instance;
+ }
+
+ void stopTrustEngineListener() {
+ authorizationTracker.close();
+ instance = null;
+ }
+
+ public void addedTrustAnchor(Certificate anchor) {
+ // find any SignedContent with SignerInfos that do not have an anchor;
+ // re-evaluate trust and check authorization for these SignedContents
+ Bundle[] bundles = context.getBundles();
+ Set<Bundle> unresolved = new HashSet<Bundle>();
+ for (int i = 0; i < bundles.length; i++) {
+ SignedContentImpl signedContent = getSignedContent(bundles[i]);
+ if (signedContent != null && signedContent.isSigned()) {
+ // check the SignerInfos for this content
+ SignerInfo[] infos = signedContent.getSignerInfos();
+ for (int j = 0; j < infos.length; j++) {
+ if (infos[j].getTrustAnchor() == null)
+ // one of the signers is not trusted
+ unresolved.add(bundles[i]);
+ SignerInfo tsa = signedContent.getTSASignerInfo(infos[j]);
+ if (tsa != null && tsa.getTrustAnchor() == null)
+ // one of the tsa signers is not trusted
+ unresolved.add(bundles[i]);
+ }
+ }
+ if (unresolved.contains(bundles[i])) {
+ // found an untrusted signer for this bundle re-evaluate trust
+ SignedBundleFile.determineTrust(signedContent, SignedBundleHook.VERIFY_TRUST);
+ // now check the authorization handler
+ checkAuthorization(signedContent, bundles[i]);
+ }
+ }
+ // try to resolve
+ if (unresolved.size() > 0)
+ resolveBundles(unresolved.toArray(new Bundle[unresolved.size()]), false);
+ }
+
+ private void checkAuthorization(SignedContentImpl signedContent, Bundle bundle) {
+ AuthorizationEngine authEngine = getAuthorizationEngine();
+ if (authEngine != null)
+ authEngine.authorize(signedContent, bundle);
+ }
+
+ AuthorizationEngine getAuthorizationEngine() {
+ return authorizationTracker.getService();
+ }
+
+ private void resolveBundles(Bundle[] bundles, boolean refresh) {
+ ServiceReference<?> ref = context.getServiceReference(PackageAdmin.class.getName());
+ if (ref == null)
+ return;
+ PackageAdmin pa = (PackageAdmin) context.getService(ref);
+ if (pa == null)
+ return;
+ try {
+ if (refresh)
+ pa.refreshPackages(bundles);
+ else
+ pa.resolveBundles(bundles);
+ } finally {
+ context.ungetService(ref);
+ }
+ }
+
+ public void removedTrustAnchor(Certificate anchor) {
+ // find any signed content that has signerinfos with the supplied anchor
+ // re-evaluate trust and check authorization again.
+ Bundle[] bundles = context.getBundles();
+ Set<Bundle> usingAnchor = new HashSet<Bundle>();
+ Set<SignerInfo> untrustedSigners = new HashSet<SignerInfo>();
+ for (int i = 0; i < bundles.length; i++) {
+ SignedContentImpl signedContent = getSignedContent(bundles[i]);
+ if (signedContent != null && signedContent.isSigned()) {
+ // check signer infos for this content
+ SignerInfo[] infos = signedContent.getSignerInfos();
+ for (int j = 0; j < infos.length; j++) {
+ if (anchor.equals(infos[j].getTrustAnchor())) {
+ // one of the signers uses this anchor
+ untrustedSigners.add(infos[j]);
+ usingAnchor.add(bundles[i]);
+ }
+ SignerInfo tsa = signedContent.getTSASignerInfo(infos[j]);
+ if (tsa != null && anchor.equals(tsa.getTrustAnchor())) {
+ // one of the tsa signers uses this anchor
+ usingAnchor.add(bundles[i]);
+ untrustedSigners.add(tsa);
+ }
+ }
+ }
+ }
+ // remove trust anchors from untrusted signers
+ for (Iterator<SignerInfo> untrusted = untrustedSigners.iterator(); untrusted.hasNext();)
+ ((SignerInfoImpl) untrusted.next()).setTrustAnchor(null);
+ // re-establish trust and check authorization
+ for (Iterator<Bundle> untrustedBundles = usingAnchor.iterator(); untrustedBundles.hasNext();) {
+ Bundle bundle = untrustedBundles.next();
+ SignedContentImpl signedContent = getSignedContent(bundle);
+ // found an signer using the anchor for this bundle re-evaluate trust
+ SignedBundleFile.determineTrust(signedContent, SignedBundleHook.VERIFY_TRUST);
+ // now check the authorization handler
+ checkAuthorization(signedContent, bundle);
+ }
+ // TODO an optimization here would be to check for real DisabledInfo objects for each bundle
+ // try to refresh
+ if (usingAnchor.size() > 0)
+ resolveBundles(usingAnchor.toArray(new Bundle[usingAnchor.size()]), true);
+ }
+
+ private SignedContentImpl getSignedContent(Bundle bundle) {
+ BaseData data = (BaseData) ((AbstractBundle) bundle).getBundleData();
+ SignedStorageHook hook = (SignedStorageHook) data.getStorageHook(SignedStorageHook.KEY);
+ if (hook == null)
+ return null;
+ return (SignedContentImpl) hook.getSignedContent();
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/launch/Equinox.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/launch/Equinox.java
new file mode 100644
index 000000000..d24ff52a2
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/launch/Equinox.java
@@ -0,0 +1,301 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.launch;
+
+import java.io.*;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.net.*;
+import java.security.*;
+import java.security.cert.X509Certificate;
+import java.util.*;
+import org.eclipse.osgi.framework.internal.core.FrameworkProperties;
+import org.eclipse.osgi.framework.util.Headers;
+import org.eclipse.osgi.internal.baseadaptor.DevClassPathHelper;
+import org.eclipse.osgi.util.ManifestElement;
+import org.osgi.framework.*;
+import org.osgi.framework.launch.Framework;
+
+/**
+ * The System Bundle implementation for the Equinox Framework.
+ *
+ * @since 3.5
+ */
+public class Equinox implements Framework {
+ private static final String implName = "org.eclipse.osgi.framework.internal.core.EquinoxLauncher"; //$NON-NLS-1$
+ /**@GuardedBy this*/
+ private Framework impl;
+ private final boolean useSeparateCL;
+ private final Map<String, Object> configuration;
+
+ public Equinox(Map<String, ?> configuration) {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null)
+ sm.checkPermission(new AllPermission());
+ useSeparateCL = FrameworkProperties.inUse();
+ @SuppressWarnings("unchecked")
+ final Map<String, Object> empty = Collections.EMPTY_MAP;
+ this.configuration = configuration == null ? empty : new HashMap<String, Object>(configuration);
+ }
+
+ private Framework createImpl() {
+ if (System.getSecurityManager() == null)
+ return createImpl0();
+ return AccessController.doPrivileged(new PrivilegedAction<Framework>() {
+ public Framework run() {
+ return createImpl0();
+ }
+ });
+ }
+
+ Framework createImpl0() {
+ try {
+ Class<?> implClazz = getImplClass();
+ Constructor<?> constructor = implClazz.getConstructor(new Class[] {Map.class});
+ return (Framework) constructor.newInstance(new Object[] {configuration});
+ } catch (ClassNotFoundException e) {
+ throw new NoClassDefFoundError(implName);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ } catch (NoSuchMethodException e) {
+ throw new NoSuchMethodError(e.getMessage());
+ } catch (InstantiationException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+
+ private Class<?> getImplClass() throws ClassNotFoundException {
+ ClassLoader thisCL = this.getClass().getClassLoader();
+ if (!(useSeparateCL && (thisCL instanceof URLClassLoader)))
+ return Class.forName(implName);
+ URL[] cp = getFrameworkURLs((URLClassLoader) thisCL);
+ EquinoxFWClassLoader fwCL = new EquinoxFWClassLoader(cp, thisCL);
+ return fwCL.loadClass(implName);
+ }
+
+ private URL[] getFrameworkURLs(URLClassLoader frameworkLoader) {
+ // use the classpath of the framework class loader
+ URL[] cp = frameworkLoader.getURLs();
+ List<URL> result = new ArrayList<URL>(cp.length);
+ for (int i = 0; i < cp.length; i++) {
+ // need to add only the urls for the framework and any framework fragments
+ InputStream manifest = null;
+ try {
+ if (cp[i].getFile().endsWith("/")) { //$NON-NLS-1$
+ manifest = new URL(cp[i], org.eclipse.osgi.framework.internal.core.Constants.OSGI_BUNDLE_MANIFEST).openStream();
+ } else {
+ manifest = new URL("jar:" + cp[i].toExternalForm() + "!/" + org.eclipse.osgi.framework.internal.core.Constants.OSGI_BUNDLE_MANIFEST).openStream(); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ Map<String, String> headers = ManifestElement.parseBundleManifest(manifest, new Headers<String, String>(10));
+ String bsnSpec = getValue(headers, Constants.BUNDLE_SYMBOLICNAME);
+ if (bsnSpec == null)
+ continue;
+ String internalBSN = org.eclipse.osgi.framework.internal.core.Constants.getInternalSymbolicName();
+ if (internalBSN.equals(bsnSpec)) {
+ // this is the framework
+ addDevClassPaths(cp[i], bsnSpec, result);
+ result.add(cp[i]);
+ } else {
+ if (!isFrameworkFragment(headers, internalBSN))
+ continue;
+ // this is for a framework extension
+ addDevClassPaths(cp[i], bsnSpec, result);
+ result.add(cp[i]);
+ }
+ } catch (IOException e) {
+ continue; // no manifest;
+ } catch (BundleException e) {
+ continue; // bad manifest;
+ } finally {
+ if (manifest != null)
+ try {
+ manifest.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+ return result.toArray(new URL[result.size()]);
+ }
+
+ private void addDevClassPaths(URL cp, String bsn, List<URL> result) {
+ if (!cp.getPath().endsWith("/")) //$NON-NLS-1$
+ return;
+ String[] devPaths = DevClassPathHelper.getDevClassPath(bsn);
+ if (devPaths == null)
+ return;
+ for (int i = 0; i < devPaths.length; i++)
+ try {
+ char lastChar = devPaths[i].charAt(devPaths[i].length() - 1);
+ URL url;
+ if ((devPaths[i].endsWith(".jar") || (lastChar == '/' || lastChar == '\\'))) //$NON-NLS-1$
+ url = new URL(cp, devPaths[i]);
+ else
+ url = new URL(cp, devPaths[i] + "/"); //$NON-NLS-1$
+ result.add(url);
+ } catch (MalformedURLException e) {
+ continue;
+ }
+ }
+
+ private boolean isFrameworkFragment(Map<String, String> headers, String internalBSN) {
+ String hostBSN = getValue(headers, Constants.FRAGMENT_HOST);
+ return internalBSN.equals(hostBSN) || Constants.SYSTEM_BUNDLE_SYMBOLICNAME.equals(hostBSN);
+ }
+
+ private String getValue(Map<String, String> headers, String key) {
+ String headerSpec = headers.get(key);
+ if (headerSpec == null)
+ return null;
+ ManifestElement[] elements;
+ try {
+ elements = ManifestElement.parseHeader(key, headerSpec);
+ } catch (BundleException e) {
+ return null;
+ }
+ if (elements == null)
+ return null;
+ return elements[0].getValue();
+ }
+
+ private synchronized Framework getImpl() {
+ if (impl == null)
+ impl = createImpl();
+ return impl;
+ }
+
+ public void init() throws BundleException {
+ getImpl().init();
+ }
+
+ public FrameworkEvent waitForStop(long timeout) throws InterruptedException {
+ return getImpl().waitForStop(timeout);
+ }
+
+ public Enumeration<URL> findEntries(String path, String filePattern, boolean recurse) {
+ return getImpl().findEntries(path, filePattern, recurse);
+ }
+
+ public BundleContext getBundleContext() {
+ return getImpl().getBundleContext();
+ }
+
+ public long getBundleId() {
+ return getImpl().getBundleId();
+ }
+
+ public URL getEntry(String path) {
+ return getImpl().getEntry(path);
+ }
+
+ public Enumeration<String> getEntryPaths(String path) {
+ return getImpl().getEntryPaths(path);
+ }
+
+ public Dictionary<String, String> getHeaders() {
+ return getImpl().getHeaders();
+ }
+
+ public Dictionary<String, String> getHeaders(String locale) {
+ return getImpl().getHeaders(locale);
+ }
+
+ public long getLastModified() {
+ return getImpl().getLastModified();
+ }
+
+ public String getLocation() {
+ return getImpl().getLocation();
+ }
+
+ public ServiceReference<?>[] getRegisteredServices() {
+ return getImpl().getRegisteredServices();
+ }
+
+ public URL getResource(String name) {
+ return getImpl().getResource(name);
+ }
+
+ public Enumeration<URL> getResources(String name) throws IOException {
+ return getImpl().getResources(name);
+ }
+
+ public ServiceReference<?>[] getServicesInUse() {
+ return getImpl().getServicesInUse();
+ }
+
+ public int getState() {
+ return getImpl().getState();
+ }
+
+ public String getSymbolicName() {
+ return getImpl().getSymbolicName();
+ }
+
+ public boolean hasPermission(Object permission) {
+ return getImpl().hasPermission(permission);
+ }
+
+ public Class<?> loadClass(String name) throws ClassNotFoundException {
+ return getImpl().loadClass(name);
+ }
+
+ public void start(int options) throws BundleException {
+ getImpl().start(options);
+ }
+
+ public void start() throws BundleException {
+ getImpl().start();
+ }
+
+ public void stop(int options) throws BundleException {
+ getImpl().stop(options);
+ }
+
+ public void stop() throws BundleException {
+ getImpl().stop();
+ }
+
+ public void uninstall() throws BundleException {
+ getImpl().uninstall();
+ }
+
+ public void update() throws BundleException {
+ getImpl().update();
+ }
+
+ public void update(InputStream in) throws BundleException {
+ getImpl().update(in);
+ }
+
+ public Map<X509Certificate, List<X509Certificate>> getSignerCertificates(int signersType) {
+ return getImpl().getSignerCertificates(signersType);
+ }
+
+ public Version getVersion() {
+ return getImpl().getVersion();
+ }
+
+ public <A> A adapt(Class<A> adapterType) {
+ return getImpl().adapt(adapterType);
+ }
+
+ public int compareTo(Bundle o) {
+ return getImpl().compareTo(o);
+ }
+
+ public File getDataFile(String filename) {
+ return getImpl().getDataFile(filename);
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/launch/EquinoxFWClassLoader.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/launch/EquinoxFWClassLoader.java
new file mode 100644
index 000000000..7011b5b13
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/launch/EquinoxFWClassLoader.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.launch;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+
+class EquinoxFWClassLoader extends URLClassLoader {
+
+ private static final String[] DELEGATE_PARENT_FIRST = {"java.", "org.osgi.", "org.eclipse.osgi.launch.", "org.eclipse.osgi.service.", "org.eclipse.osgi.framework.log", "org.eclipse.osgi.framework.adaptor", "org.eclipse.osgi.framework.internal.core.ReferenceInputStream"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$
+ private static final String[] DELEGATE_CHILD_FIRST = new String[0]; // nothing right now is skipped
+
+ private final ClassLoader parent;
+
+ public EquinoxFWClassLoader(URL[] urls, ClassLoader parent) {
+ super(urls, parent);
+ this.parent = parent;
+ }
+
+ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ Class<?> clazz = findLoadedClass(name);
+ if (clazz != null)
+ return clazz;
+
+ boolean childFirst = childFirst(name);
+ ClassNotFoundException cnfe = null;
+
+ if (childFirst)
+ try {
+ clazz = findClass(name);
+ } catch (ClassNotFoundException e) {
+ // continue
+ cnfe = e;
+ }
+
+ if (clazz == null)
+ try {
+ clazz = parent.loadClass(name);
+ } catch (ClassNotFoundException e) {
+ // continue
+ }
+
+ if (clazz == null && cnfe != null)
+ throw cnfe;
+ if (clazz == null && !childFirst)
+ clazz = findClass(name);
+
+ if (resolve)
+ resolveClass(clazz);
+ return clazz;
+ }
+
+ private boolean childFirst(String name) {
+ for (int i = DELEGATE_CHILD_FIRST.length - 1; i >= 0; i--)
+ if (name.startsWith(DELEGATE_CHILD_FIRST[i]))
+ return true;
+ for (int i = DELEGATE_PARENT_FIRST.length - 1; i >= 0; i--)
+ if (name.startsWith(DELEGATE_PARENT_FIRST[i]))
+ return false;
+ return true;
+
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/launch/EquinoxFactory.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/launch/EquinoxFactory.java
new file mode 100644
index 000000000..389f07a7a
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/launch/EquinoxFactory.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.launch;
+
+import java.util.Map;
+import org.osgi.framework.launch.Framework;
+import org.osgi.framework.launch.FrameworkFactory;
+
+/**
+ * The framework factory implementation for the Equinox framework.
+ * @since 3.5
+ */
+public class EquinoxFactory implements FrameworkFactory {
+
+ public Framework newFramework(Map<String, String> configuration) {
+ return new Equinox(configuration);
+ }
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/launch/package.html b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/launch/package.html
new file mode 100644
index 000000000..b2d671be0
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/launch/package.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Package-level Javadoc</title>
+</head>
+<body>
+Provides the Equinox framework launch API.
+<h2>
+Package Specification</h2>
+This package specifies the Equinox framework launch API.
+<p>
+Clients that want to launch an embedded instance of Equinox
+will likely be interested in the types provided by this package.
+</p>
+</body>
+</html>
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/pluginconversion/PluginConversionException.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/pluginconversion/PluginConversionException.java
new file mode 100644
index 000000000..6aa16c032
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/pluginconversion/PluginConversionException.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.service.pluginconversion;
+
+/**
+ * Custom exception for errors that can happen during plugin conversion.
+ *
+ * @since 3.0
+ */
+public class PluginConversionException extends Exception {
+ private static final long serialVersionUID = 3258130258472284472L;
+
+ /**
+ * Nested exception.
+ */
+ private transient Throwable cause;
+
+ /**
+ * Constructor for the class.
+ */
+ public PluginConversionException() {
+ super();
+ }
+
+ /**
+ * Create a new exception with the given message.
+ *
+ * @param message the message for the exception
+ */
+ public PluginConversionException(String message) {
+ super(message);
+ }
+
+ /**
+ * Create a new exception with the given message and nested exception.
+ *
+ * @param message the message for the exception
+ * @param cause the nested exception
+ */
+ public PluginConversionException(String message, Throwable cause) {
+ super(message);
+ this.cause = cause;
+ }
+
+ /**
+ * Create a new exception with the given nested exception.
+ *
+ * @param cause the nested exception
+ */
+ public PluginConversionException(Throwable cause) {
+ this.cause = cause;
+ }
+
+ /**
+ * Return the nested exception for this exception or <code>null</code>
+ * if there is none.
+ *
+ * @return the nested exception or <code>null</code>
+ */
+ public Throwable getCause() {
+ return cause;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/pluginconversion/PluginConverter.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/pluginconversion/PluginConverter.java
new file mode 100644
index 000000000..a5be77a3a
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/pluginconversion/PluginConverter.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.service.pluginconversion;
+
+import java.io.File;
+import java.util.Dictionary;
+
+/**
+ * A plug-in convertor is able to convert plug-in manifest files (<tt>plugin.xml</tt>) and
+ * fragment manifest files (<tt>fragment.xml</tt>) to bundle manifest files (<tt>MANIFEST.MF</tt>).
+ * <p>
+ * This interface is not intended to be implemented by clients.
+ * </p>
+ *
+ * @since 3.0
+ */
+public interface PluginConverter {
+ /**
+ * Converts a plug-in/fragment manifest at the given source base location (a directory or jar file) and
+ * generates a corresponding bundle manifest at the given default target location (a file).
+ *
+ * @param pluginBaseLocation the base location for the plug-in/fragment manifest to be converted
+ * (a directory or jar file, e.g. the plug-in install location)
+ * @param bundleManifestLocation the location for the bundle manifest to be generated
+ * (including the file name).
+ * @param compatibilityManifest a boolean indicating if the manifest should contain headers to run
+ * in backward compatibility
+ * @param target a string indicating the version of the runtime for which the manifest generated is targeted
+ * @param analyseJars a boolean indicating if the code jars of the given plugin must be analysed. When set to false the Export-Package header will not be set in the bundle manifest.
+ * @param devProperties a dictionary of development time classpath properties. The dictionary contains a mapping from plugin id to development
+ * time classpath. A value of <code>null</code> indicates that the default development time classpath properties will be used.
+ * @return the generated manifest file location, if a bundle manifest was successfully
+ * generated (or already existed), <code>null</code> otherwise.
+ * @throws PluginConversionException if an error occurs while converting the manifest
+ */
+ public File convertManifest(File pluginBaseLocation, File bundleManifestLocation, boolean compatibilityManifest, String target, boolean analyseJars, Dictionary<String, String> devProperties) throws PluginConversionException;
+
+ /**
+ * Converts a plug-in/fragment manifest at the given source base location (a directory or jar file) and
+ * generates a corresponding bundle manifest returned as a dictionary.
+ *
+ * @param pluginBaseLocation the base location for the plug-in/fragment manifest to be converted
+ * (a directory or jar file, e.g. the plug-in install location)
+ * @param compatibility a boolean indicating if the manifest should contain headers to run
+ * in backward compatibility
+ * @param target a string indicating the version of the runtime for which the manifest generated is targeted
+ * @param analyseJars a boolean indicating if the code jars of the given plugin must be analysed. When set to false the Export-Package header will not be set in the bundle manifest.
+ * @param devProperties a dictionary of development time classpath properties. The dictionary contains a mapping from plugin id to development
+ * time classpath. A value of <code>null</code> indicates that the default development time classpath properties will be used.
+ * @return the generated manifest as a dictionary, if a bundle manifest was successfully
+ * generated, <code>null</code> otherwise
+ * @throws PluginConversionException if an error occurs while converting the manifest
+ */
+ public Dictionary<String, String> convertManifest(File pluginBaseLocation, boolean compatibility, String target, boolean analyseJars, Dictionary<String, String> devProperties) throws PluginConversionException;
+
+ /**
+ * Construct a bundle manifest file from the given dictionary and write it out to the
+ * specified location in the file system.
+ * <p>
+ * If the <code>compatibilityManifest</code> parameter is <code>true</code> then
+ * the generated manifest will include the necessary headers to all the manifest to be
+ * run in backwards compatibility mode.
+ * </p>
+ *
+ * @param generationLocation the location for the bundle manifest to be written
+ * @param manifestToWrite the dictionary to write into generationLocation file
+ * @param compatibilityManifest a boolean indicating if the file should contain headers to allow
+ * running in backward compatibility mode
+ * @throws PluginConversionException if an error occurs while writing the given manifest
+ */
+ public void writeManifest(File generationLocation, Dictionary<String, String> manifestToWrite, boolean compatibilityManifest) throws PluginConversionException;
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/BaseDescription.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/BaseDescription.java
new file mode 100644
index 000000000..3c70068a9
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/BaseDescription.java
@@ -0,0 +1,97 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.service.resolver;
+
+import java.util.Map;
+import org.osgi.framework.Version;
+import org.osgi.framework.wiring.BundleCapability;
+
+/**
+ * This class represents a base description object for a state. All description
+ * objects in a state have a name and a version.
+ * <p>
+ * This interface is not intended to be implemented by clients. The
+ * {@link StateObjectFactory} should be used to construct instances.
+ * </p>
+ * @since 3.1
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface BaseDescription {
+ /**
+ * Returns the name.
+ * @return the name
+ */
+ public String getName();
+
+ /**
+ * Returns the version.
+ * @return the version
+ */
+ public Version getVersion();
+
+ /**
+ * Returns the bundle which supplies this base description
+ * @return the bundle which supplies this base description
+ * @since 3.2
+ */
+ public BundleDescription getSupplier();
+
+ /**
+ * Returns the directives declared with the description.
+ * This will return all known directives for the type of description.
+ * The set of directives differs for each description type.
+ * @return the known directives declared with the description
+ * @since 3.7
+ */
+ public Map<String, String> getDeclaredDirectives();
+
+ /**
+ * Returns the attributes declared with the description.
+ * This will return all known attributes for the type of description.
+ * The set of attributes differs for each description type.
+ * @return the attributes declared with the description
+ * @since 3.7
+ */
+ public Map<String, Object> getDeclaredAttributes();
+
+ /**
+ * Returns the capability represented by this description.
+ * Some descriptions types may not be able to represent
+ * a capability. In such cases <code>null</code> is
+ * returned.
+ * @return the capability represented by this base description
+ * @since 3.7
+ */
+ public BundleCapability getCapability();
+
+ /**
+ * Returns the user object associated to this description, or
+ * <code>null</code> if none exists.
+ *
+ * @return the user object associated to this description,
+ * or <code>null</code>
+ * @since 3.8
+ */
+ public Object getUserObject();
+
+ /**
+ * Associates a user-provided object to this description, or
+ * removes an existing association, if <code>null</code> is provided. The
+ * provided object is not interpreted in any ways by this
+ * description.
+ *
+ * @param userObject an arbitrary object provided by the user, or
+ * <code>null</code>
+ * @since 3.8
+ */
+ public void setUserObject(Object userObject);
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/BundleDelta.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/BundleDelta.java
new file mode 100644
index 000000000..f343f1d49
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/BundleDelta.java
@@ -0,0 +1,126 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.service.resolver;
+
+/**
+ * BundleDeltas represent the changes related to an individual bundle between two
+ * states.
+ * <p>
+ * This interface is not intended to be implemented by clients. The
+ * {@link StateObjectFactory} should be used to construct instances.
+ * </p>
+ * @since 3.1
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface BundleDelta extends Comparable<BundleDelta> {
+
+ /**
+ * Delta type constant (bit mask) indicating that the bundle has been added
+ * to the new state.
+ * @see BundleDelta#getType
+ */
+ public static final int ADDED = 0x1;
+ /**
+ * Delta type constant (bit mask) indicating that the bundle is no longer present in
+ * the new state.
+ * @see BundleDelta#getType
+ */
+ public static final int REMOVED = 0x2;
+ /**
+ * Delta type constant (bit mask) indicating that the bundle has been updated
+ * between the old and new state. Note that an update delta may in fact represent
+ * a downgrading of the bundle to a previous version.
+ * @see BundleDelta#getType
+ */
+ public static final int UPDATED = 0x4;
+ /**
+ * Delta type constant (bit mask) indicating that the bundle has become resolved
+ * in the new state.
+ * @see BundleDelta#getType
+ */
+ public static final int RESOLVED = 0x8;
+ /**
+ * Delta type constant (bit mask) indicating that the bundle has become unresolved
+ * in the new state. Note that newly added bundles are unresolved by default and
+ * as such, do not transition to unresolved state so this flag is not set.
+ * @see BundleDelta#getType
+ */
+ public static final int UNRESOLVED = 0x10;
+ /**
+ * Delta type constant (bit mask) indicating that the bundles and packages which this
+ * bundle requires/imports (respectively) have changed in the new state.
+ * @see BundleDelta#getType
+ * @deprecated this type is no longer valid
+ */
+ public static final int LINKAGE_CHANGED = 0x20;
+
+ /**
+ * Delta type constant (bit mask) indicating that the bundles which this
+ * bundle optionally requires have changed in the new state.
+ * @see BundleDelta#getType
+ * @deprecated this type is no longer valid
+ */
+ public static final int OPTIONAL_LINKAGE_CHANGED = 0x40;
+
+ /**
+ * Delta type constant (bit mask) indicating that the this bundle is
+ * pending a removal. Note that bundles with this flag set will also
+ * have the {@link BundleDelta#REMOVED} flag set. A bundle will have
+ * this flag set if it has been removed from the state but has other
+ * existing bundles in the state that depend on it.
+ * @see BundleDelta#getType
+ */
+ public static final int REMOVAL_PENDING = 0x80;
+
+ /**
+ * Delta type constant (bit mask) indicating that the this bundle has
+ * completed a pending removal. A bundle will complete a pending removal only
+ * after it has been re-resolved by the resolver.
+ */
+ public static final int REMOVAL_COMPLETE = 0x100;
+
+ /**
+ * Returns the BundleDescription that this bundle delta is for.
+ * @return the BundleDescription that this bundle delta is for.
+ */
+ public BundleDescription getBundle();
+
+ /**
+ * Returns the type of change which occured. The return value is composed
+ * of by bit-wise masking the relevant flags from the set ADDED, REMOVED,
+ * UPDATED, RESOLVED, UNRESOLVED, LINKAGE_CHANGED, REMOVAL_PENDING, REMOVAL_COMPLETE.
+ * Note that bundle start and stop state changes are not captured in the
+ * delta as they do not represent structural changes but rather transient
+ * runtime states.
+ * @return the type of change which occured
+ */
+ public int getType();
+
+ /**
+ * Answers an integer indicating the relative positions of the receiver and
+ * the argument in the natural order of elements of the receiver's class.
+ * <p>
+ * The natural order of elements is determined by the bundle id of the
+ * BundleDescription that this bundle delta is for.
+ *
+ * @return int which should be <0 if the receiver should sort before the
+ * argument, 0 if the receiver should sort in the same position as
+ * the argument, and >0 if the receiver should sort after the
+ * argument.
+ * @param obj
+ * another BundleDelta an object to compare the receiver to
+ * @exception ClassCastException
+ * if the argument can not be converted into something
+ * comparable with the receiver.
+ * @since 3.7
+ */
+ public int compareTo(BundleDelta obj);
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/BundleDescription.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/BundleDescription.java
new file mode 100644
index 000000000..b7acfcc21
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/BundleDescription.java
@@ -0,0 +1,271 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.service.resolver;
+
+import java.util.Map;
+import org.osgi.framework.wiring.BundleRevision;
+
+/**
+ * This class represents a specific version of a bundle in the system.
+ * <p>
+ * This interface is not intended to be implemented by clients. The
+ * {@link StateObjectFactory} should be used to construct instances.
+ * </p>
+ * @since 3.1
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface BundleDescription extends BaseDescription, BundleRevision {
+
+ /**
+ * Gets the Bundle-SymbolicName of this BundleDescription.
+ * Same as calling {@link BaseDescription#getName()}.
+ * @return The bundle symbolic name or null if the bundle
+ * does not have a symbolic name.
+ */
+ public String getSymbolicName();
+
+ /**
+ * Returns the arbitrary attributes for this bundle description.
+ * @return the arbitrary attributes for this bundle description
+ * @since 3.7
+ */
+ public Map<String, Object> getAttributes();
+
+ /**
+ * The location string for this bundle.
+ * @return The bundle location or null if the bundle description
+ * does not have a location
+ */
+ public String getLocation();
+
+ /**
+ * Returns an array of bundle specifications defined by the Require-Bundle
+ * clause in this bundle.
+ *
+ * @return an array of bundle specifications
+ */
+ public BundleSpecification[] getRequiredBundles();
+
+ /**
+ * Returns an array of export package descriptions defined by the Export-Package clauses.
+ * All export package descriptions are returned even if they have not been selected by
+ * the resolver as an exporter of the package.
+ *
+ * @return an array of export package descriptions
+ */
+ public ExportPackageDescription[] getExportPackages();
+
+ /**
+ * Returns an array of import package specifications defined by the Import-Package clause.
+ * @return an array of import package specifications
+ */
+ public ImportPackageSpecification[] getImportPackages();
+
+ /**
+ * Returns an array of dynamic import package specifications that have been added
+ * dynamically to this bundle description.
+ * @return an array of dynamic import package specifications
+ * @see State#addDynamicImportPackages(BundleDescription, ImportPackageSpecification[])
+ * @since 3.7
+ */
+ public ImportPackageSpecification[] getAddedDynamicImportPackages();
+
+ /**
+ * Returns an array of generic specifications constraints required by this bundle.
+ * @return an array of generic specifications
+ * @since 3.2
+ */
+ public GenericSpecification[] getGenericRequires();
+
+ /**
+ * Returns an array of generic descriptions for the capabilities of this bundle.
+ * @return an array of generic descriptions
+ * @since 3.2
+ */
+ public GenericDescription[] getGenericCapabilities();
+
+ /**
+ * Returns true if this bundle has one or more dynamically imported packages.
+ * @return true if this bundle has one or more dynamically imported packages.
+ */
+ public boolean hasDynamicImports();
+
+ /**
+ * Returns all the exported packages from this bundle that have been selected by
+ * the resolver. The returned list will include the ExportPackageDescriptions
+ * returned by {@link #getExportPackages()} that have been selected by the resolver and
+ * packages which are propagated by this bundle.
+ * @return the selected list of packages that this bundle exports. If the bundle is
+ * unresolved or has no shared packages then an empty array is returned.
+ */
+ public ExportPackageDescription[] getSelectedExports();
+
+ /**
+ * Returns all the capabilities provided by ths bundle that have been selected by
+ * the resolver. The returned list will include the capabilities
+ * returned by {@link #getGenericCapabilities()} that have been selected by the
+ * resolver and any capabilities provided by fragments attached to this bundle.
+ * @return the selected capabilities that this bundle provides. If the bundle is
+ * unresolved or has no capabilities then an empty array is returned.
+ * @since 3.7
+ */
+ public GenericDescription[] getSelectedGenericCapabilities();
+
+ /**
+ * Returns all the bundle descriptions that satisfy all the require bundles for this bundle.
+ * If the bundle is not resolved or the bundle does not require any bundles then an empty array is
+ * returned.
+ * @return the bundles descriptions that satisfy all the require bundles for this bundle.
+ */
+ public BundleDescription[] getResolvedRequires();
+
+ /**
+ * Returns all the export packages that satisfy all the imported packages for this bundle.
+ * If the bundle is not resolved or the bundle does not import any packages then an empty array is
+ * returned.
+ * @return the exported packages that satisfy all the imported packages for this bundle.
+ */
+ public ExportPackageDescription[] getResolvedImports();
+
+ /**
+ * Returns all the capabilities that satisfy all the capability requirements for this
+ * bundle. This includes any capabilities required by fragments attached to this bundle.
+ * @return the capabilities that satisfy all the capability requirements for this bundle.
+ * If the bundle is unresolved or has no capability requirements then an empty array is
+ * returned.
+ * @since 3.7
+ */
+ public GenericDescription[] getResolvedGenericRequires();
+
+ /**
+ * Returns true if this bundle is resolved in its host state.
+ *
+ * @return true if this bundle is resolved in its host state.
+ */
+ public boolean isResolved();
+
+ /**
+ * Returns the state object which hosts this bundle. null is returned if
+ * this bundle is not currently in a state.
+ *
+ * @return the state object which hosts this bundle.
+ */
+ public State getContainingState();
+
+ /**
+ * Returns the string representation of this bundle.
+ *
+ * @return String representation of this bundle.
+ */
+ public String toString();
+
+ /**
+ * Returns the host for this bundle. null is returned if this bundle is not
+ * a fragment.
+ *
+ * @return the host for this bundle.
+ */
+ public HostSpecification getHost();
+
+ /**
+ * Returns the numeric id of this bundle. Typically a bundle description
+ * will only have a numeric id if it represents a bundle that is installed in a
+ * framework as the framework assigns the ids. -1 is returned if the id is not known.
+ *
+ * @return the numeric id of this bundle description
+ */
+ public long getBundleId();
+
+ /**
+ * Returns all fragments known to this bundle (regardless resolution status).
+ *
+ * @return an array of BundleDescriptions containing all known fragments
+ */
+ public BundleDescription[] getFragments();
+
+ /**
+ * Returns whether this bundle is a singleton. Singleton bundles require
+ * that at most one single version of the bundle can be resolved at a time.
+ * <p>
+ * The existence of a single bundle marked as singleton causes all bundles
+ * with the same symbolic name to be treated as singletons as well.
+ * </p>
+ *
+ * @return <code>true</code>, if this bundle is a singleton,
+ * <code>false</code> otherwise
+ */
+ public boolean isSingleton();
+
+ /**
+ * Returns whether this bundle is pending a removal. A bundle is pending
+ * removal if it has been removed from the state but other bundles in
+ * the state currently depend on it.
+ * @return <code>true</code>, if this bundle is pending a removal,
+ * <code>false</code> otherwise
+ */
+ public boolean isRemovalPending();
+
+ /**
+ * Returns all bundles which depend on this bundle. A bundle depends on
+ * another bundle if it requires the bundle, imports a package which is
+ * exported by the bundle, is a fragment to the bundle or is the host
+ * of the bundle.
+ * @return all bundles which depend on this bundle.
+ */
+ public BundleDescription[] getDependents();
+
+ /**
+ * Returns the platform filter in the form of an LDAP filter
+ * @return the platfomr filter in the form of an LDAP filter
+ */
+ public String getPlatformFilter();
+
+ /**
+ * Returns true if this bundle allows fragments to attach
+ * @return true if this bundle allows fragments to attach
+ */
+ public boolean attachFragments();
+
+ /**
+ * Returns true if this bundle allows fragments to attach dynamically
+ * after it has been resolved.
+ * @return true if this bundle allows fragments to attach dynamically
+ */
+ public boolean dynamicFragments();
+
+ /**
+ * Returns the list of execution environments that are required by
+ * this bundle. Any one of the listed execution environments will
+ * allow this bundle to be resolved.
+ * @since 3.2
+ * @return the list of execution environments that are required.
+ */
+ public String[] getExecutionEnvironments();
+
+ /**
+ * Returns the native code specification for this bundle. A value
+ * of <code>null</code> is returned if there is no native code
+ * specification.
+ * @return the native code specification.
+ * @since 3.4
+ */
+ public NativeCodeSpecification getNativeCodeSpecification();
+
+ /**
+ * Returns the export packages that satisfy imported packages for this bundle description
+ * and substitute one of the exports for this bundle description. If the bundle is not resolved
+ * or the bundle does not have substituted exports then an empty array is
+ * returned.
+ * @return all substituted exports for this bundle description
+ * @since 3.4
+ */
+ public ExportPackageDescription[] getSubstitutedExports();
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/BundleSpecification.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/BundleSpecification.java
new file mode 100644
index 000000000..bb5d4ac23
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/BundleSpecification.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.service.resolver;
+
+/**
+ * A representation of one bundle import constraint as seen in a
+ * bundle manifest and managed by a state and resolver.
+ * <p>
+ * This interface is not intended to be implemented by clients. The
+ * {@link StateObjectFactory} should be used to construct instances.
+ * </p>
+ * @since 3.1
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface BundleSpecification extends VersionConstraint {
+
+ /**
+ * Returns whether or not this bundle specificiation is exported from the
+ * declaring bundle.
+ *
+ * @return whether this specification is exported
+ */
+ public boolean isExported();
+
+ /**
+ * Returns whether or not this bundle specificiation is optional.
+ *
+ * @return whether this specification is optional
+ */
+ public boolean isOptional();
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/DisabledInfo.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/DisabledInfo.java
new file mode 100644
index 000000000..9f79ddbda
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/DisabledInfo.java
@@ -0,0 +1,91 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.service.resolver;
+
+/**
+ * A disabled info represents a policy decision to disable a bundle which exists in a {@link State}.
+ * Bundles may be disabled by adding disabled info with the {@link State#addDisabledInfo(DisabledInfo)}
+ * method and enabled by removing disabled info with the {@link State#removeDisabledInfo(DisabledInfo)} method.
+ * A bundle is not considered to be enabled unless there are no disabled info objects for the bundle.
+ * <p>
+ * While resolving the bundle if the {@link Resolver} encounters a {@link BundleDescription} which
+ * has disabled info returned by {@link State#getDisabledInfos(BundleDescription)} then the bundle
+ * must not be allowed to resolve and a ResolverError of type {@link ResolverError#DISABLED_BUNDLE}
+ * must be added to the state.
+ * </p>
+ * @see State
+ * @since 3.4
+ */
+public final class DisabledInfo {
+ private final String policyName;
+ private final String message;
+ private final BundleDescription bundle;
+
+ /**
+ * DisabledInfo constructor.
+ * @param policyName the name of the policy
+ * @param message the message, may be <code>null</code>
+ * @param bundle the bundle
+ */
+ public DisabledInfo(String policyName, String message, BundleDescription bundle) {
+ if (policyName == null || bundle == null)
+ throw new IllegalArgumentException();
+ this.policyName = policyName;
+ this.message = message;
+ this.bundle = bundle;
+ }
+
+ /**
+ * Returns the name of the policy which disabled the bundle.
+ * @return the name of the policy
+ */
+ public String getPolicyName() {
+ return policyName;
+ }
+
+ /**
+ * Returns the message describing the reason the bundle is disabled.
+ * @return the message
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * Returns the bundle which is disabled
+ * @return the bundle which is disabled
+ */
+ public BundleDescription getBundle() {
+ return bundle;
+ }
+
+ public boolean equals(Object obj) {
+ if (obj == this)
+ return true;
+ if (!(obj instanceof DisabledInfo))
+ return false;
+ DisabledInfo other = (DisabledInfo) obj;
+ if (getBundle() == other.getBundle() && getPolicyName().equals(other.getPolicyName())) {
+ if (getMessage() == null ? other.getMessage() == null : getMessage().equals(other.getMessage()))
+ return true;
+ }
+ return false;
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (bundle == null ? 0 : bundle.hashCode());
+ result = prime * result + (policyName == null ? 0 : policyName.hashCode());
+ result = prime * result + (message == null ? 0 : message.hashCode());
+ return result;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/ExportPackageDescription.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/ExportPackageDescription.java
new file mode 100644
index 000000000..c8575762d
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/ExportPackageDescription.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.service.resolver;
+
+import java.util.Map;
+
+/**
+ * This class represents a specific version of an exported package in the system.
+ * <p>
+ * This interface is not intended to be implemented by clients. The
+ * {@link StateObjectFactory} should be used to construct instances.
+ * </p>
+ * @since 3.1
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface ExportPackageDescription extends BaseDescription {
+
+ /**
+ * Returns true if the export package is a root package; false otherwise.
+ * A ExportPackageDescription is not a root package the exporting bundle
+ * is re-exporting the package using the Reexport-Package header.
+ * @return true if the export package is a root package; false otherwise
+ * @deprecated all export package descriptions are roots. The Reexport-Package header
+ * never became API.
+ */
+ public boolean isRoot();
+
+ /**
+ * Returns the arbitrary attributes for this package.
+ * @return the arbitrary attributes for this package
+ */
+ public Map<String, Object> getAttributes();
+
+ /**
+ * Returns the directives for this package.
+ * @return the directives for this package
+ */
+ public Map<String, Object> getDirectives();
+
+ /**
+ * Returns the specified directive for this package.
+ * @param key the directive to fetch
+ * @return the specified directive for this package
+ */
+ public Object getDirective(String key);
+
+ /**
+ * Returns the exporter of this package.
+ * @return the exporter of this package.
+ */
+ public BundleDescription getExporter();
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/GenericDescription.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/GenericDescription.java
new file mode 100644
index 000000000..8f091aee0
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/GenericDescription.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.service.resolver;
+
+import java.util.Dictionary;
+import org.osgi.framework.Version;
+
+/**
+ * A description of a generic capability.
+ * @since 3.2
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface GenericDescription extends BaseDescription {
+ /**
+ * The default type of generic capability.
+ */
+ public static String DEFAULT_TYPE = "generic"; //$NON-NLS-1$
+
+ /**
+ * Returns the arbitrary attributes for this description
+ * @return the arbitrary attributes for this description
+ */
+ public Dictionary<String, Object> getAttributes();
+
+ /**
+ * Returns the type of generic description capability
+ * @return the type of generic description capability
+ */
+ public String getType();
+
+ /**
+ * This method is deprecated. Capabilities do not always have a
+ * name associated with them. All matching attributes associated
+ * with a capability are available in the attributes of a
+ * capability. This method will return the value of the
+ * attribute with the same key as this capabilities type.
+ * If this attribute's value is not a String then null is
+ * returned.
+ * @deprecated matching should only be done against a capability's
+ * attributes.
+ */
+ public String getName();
+
+ /**
+ * This method is deprecated. Capabilities do not always have a
+ * version associated with them. All matching attributes associated
+ * with a capability are available in the attributes of a
+ * capability. This method will return the value of the
+ * attribute with the key <code>"version"</code>.
+ * If this attribute's value is not a {@link Version} then null is
+ * returned.
+ * @deprecated matching should only be done against a capability's
+ * attributes.
+ */
+ public Version getVersion();
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/GenericSpecification.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/GenericSpecification.java
new file mode 100644
index 000000000..3ebc34a54
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/GenericSpecification.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.service.resolver;
+
+/**
+ * A specification which depends on a generic capability
+ * @since 3.2
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface GenericSpecification extends VersionConstraint {
+ /**
+ * The optional resolution type
+ * @see #getResolution()
+ */
+ public static final int RESOLUTION_OPTIONAL = 0x01;
+ /**
+ * The multiple resolution type
+ * @see #getResolution()
+ */
+ public static final int RESOLUTION_MULTIPLE = 0x02;
+
+ /**
+ * Returns a matching filter used to match with a suppliers attributes
+ * @return a matching filter used to match with a suppliers attributes
+ */
+ public String getMatchingFilter();
+
+ /**
+ * Returns the type of generic specification
+ * @return the type of generic specification
+ */
+ public String getType();
+
+ /**
+ * Returns the resolution type of the required capability. The returned
+ * value is a bit mask that may have the optional bit {@link #RESOLUTION_OPTIONAL}
+ * and/or the multiple bit {@link #RESOLUTION_MULTIPLE} set.
+ *
+ * @return the resolution type of the required capability
+ */
+ public int getResolution();
+
+ /**
+ * Returns the suppliers of the capability. If the the resolution is multiple then
+ * more than one supplier may be returned
+ * @return the suppliers of the capability
+ */
+ public GenericDescription[] getSuppliers();
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/HostSpecification.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/HostSpecification.java
new file mode 100644
index 000000000..3f6a46fe7
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/HostSpecification.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.service.resolver;
+
+/**
+ * A representation of one host bundle constraint as seen in a
+ * bundle manifest and managed by a state and resolver.
+ * <p>
+ * This interface is not intended to be implemented by clients. The
+ * {@link StateObjectFactory} should be used to construct instances.
+ * </p>
+ * @since 3.1
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface HostSpecification extends VersionConstraint {
+ /**
+ * Returns the list of host BundleDescriptions that satisfy this HostSpecification
+ * @return the list of host BundleDescriptions that satisfy this HostSpecification
+ */
+ public BundleDescription[] getHosts();
+
+ /**
+ * Returns if this HostSpecification is allowed to have multiple hosts
+ * @return true if this HostSpecification is allowed to have multiple hosts
+ */
+ public boolean isMultiHost();
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/ImportPackageSpecification.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/ImportPackageSpecification.java
new file mode 100644
index 000000000..09da758b1
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/ImportPackageSpecification.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.service.resolver;
+
+import java.util.Map;
+
+/**
+ * A representation of one package import constraint as seen in a
+ * bundle manifest and managed by a state and resolver.
+ * <p>
+ * This interface is not intended to be implemented by clients. The
+ * {@link StateObjectFactory} should be used to construct instances.
+ * </p>
+ * @since 3.1
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface ImportPackageSpecification extends VersionConstraint {
+ /**
+ * The static resolution directive value.
+ */
+ public static final String RESOLUTION_STATIC = "static"; //$NON-NLS-1$
+ /**
+ * The optional resolution directive value.
+ */
+ public static final String RESOLUTION_OPTIONAL = "optional"; //$NON-NLS-1$
+ /**
+ * The dynamic resolution directive value.
+ */
+ public static final String RESOLUTION_DYNAMIC = "dynamic"; //$NON-NLS-1$
+
+ /**
+ * Returns the symbolic name of the bundle this import package must be resolved to.
+ * @return the symbolic name of the bundle this import pacakge must be resolved to.
+ * A value of <code>null</code> indicates any symbolic name.
+ */
+ public String getBundleSymbolicName();
+
+ /**
+ * Returns the version range which this import package may be resolved to.
+ * @return the version range which this import package may be resolved to.
+ */
+ public VersionRange getBundleVersionRange();
+
+ /**
+ * Returns the arbitrary attributes which this import package may be resolved to.
+ * @return the arbitrary attributes which this import package may be resolved to.
+ */
+ public Map<String, Object> getAttributes();
+
+ /**
+ * Returns the directives that control this import package.
+ * @return the directives that control this import package.
+ */
+ public Map<String, Object> getDirectives();
+
+ /**
+ * Returns the specified directive that control this import package.
+ * @return the specified directive that control this import package.
+ */
+ public Object getDirective(String key);
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/NativeCodeDescription.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/NativeCodeDescription.java
new file mode 100644
index 000000000..bb6741447
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/NativeCodeDescription.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.service.resolver;
+
+import org.osgi.framework.Filter;
+
+/**
+ * This class represents a native code description.
+ * <p>
+ * This interface is not intended to be implemented by clients. The
+ * {@link StateObjectFactory} should be used to construct instances.
+ * </p>
+ * @since 3.4
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface NativeCodeDescription extends BaseDescription, Comparable<NativeCodeDescription> {
+ /**
+ * Returns the paths to the native code libraries.
+ * @return the paths to the native code libraries.
+ */
+ public String[] getNativePaths();
+
+ /**
+ * Returns the processors supported by the native code.
+ * @return the processors supported by the native code. An
+ * empty array is returned if no processors are supported.
+ */
+ public String[] getProcessors();
+
+ /**
+ * Returns the operating system names supported by the native code.
+ * @return the operating system names supported by the native code.
+ * An empty array is returned if no operating systems are supported.
+ */
+ public String[] getOSNames();
+
+ /**
+ * Returns the operating system version ranges supported by the native code.
+ * @return the operating system version ranges supported by the native code.
+ * An empty array is returned if all versions are supported.
+ */
+ public VersionRange[] getOSVersions();
+
+ /**
+ * Returns the languages supported by the native code.
+ * @return the languages supported by the native code. An empty array is
+ * returned if all languages are supported.
+ */
+ public String[] getLanguages();
+
+ /**
+ * Returns the selection filter used to select the native code.
+ * @return the selection filter used to select the native code.
+ */
+ public Filter getFilter();
+
+ /**
+ * Native code descriptions are sorted with the following preferences:
+ * <ul>
+ * <li>The minimum version of the os version ranges</li>
+ * <li>The language<li>
+ * </ul>
+ * @param other the native code description to be compared
+ * @return a negative integer, zero, or a positive integer as this natve
+ * code description is less than, equal to, or greater than the specified object.
+ * @since 3.7
+ */
+ public int compareTo(NativeCodeDescription other);
+
+ /**
+ * Indicates if this native code description has invalid native code paths. Native
+ * code paths are invalid if they can not be found in the bundle content.
+ * @return true if the native code paths are invalid; otherwise false is returned.
+ */
+ public boolean hasInvalidNativePaths();
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/NativeCodeSpecification.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/NativeCodeSpecification.java
new file mode 100644
index 000000000..1c37fd3e0
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/NativeCodeSpecification.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.service.resolver;
+
+/**
+ * This class represents a native code specification. A
+ * native code specification is different from other
+ * specification constraints which typically are resolved against
+ * suppliers provided by other bundles. A native code
+ * specification supplies it own suppliers which are matched
+ * against the platform properties at resolve time and the
+ * supplier with the best match is selected.
+ * <p>
+ * This interface is not intended to be implemented by clients. The
+ * {@link StateObjectFactory} should be used to construct instances.
+ * </p>
+ * @since 3.4
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface NativeCodeSpecification extends VersionConstraint {
+ /**
+ * Returns the list of possible suppliers to this native code specification. When
+ * this native code specification is resolved one of the possible suppliers
+ * will be selected and returned by {@link VersionConstraint#getSupplier()}.
+ * @return the list of possible suppliers.
+ */
+ public NativeCodeDescription[] getPossibleSuppliers();
+
+ /**
+ * Returns whether or not this native code specification is optional.
+ *
+ * @return whether this specification is optional
+ */
+ public boolean isOptional();
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/PlatformAdmin.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/PlatformAdmin.java
new file mode 100644
index 000000000..e7b1684ac
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/PlatformAdmin.java
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Rob Harrop - SpringSource Inc. (bug 247522)
+ *******************************************************************************/
+package org.eclipse.osgi.service.resolver;
+
+import org.osgi.framework.BundleException;
+
+/**
+ * Framework service which allows bundle programmers to inspect the bundles and
+ * packages known to the Framework. The PlatformAdmin service also allows bundles
+ * with sufficient privileges to update the state of the framework by committing a new
+ * configuration of bundles and packages.
+ *
+ * If present, there will only be a single instance of this service
+ * registered with the Framework.
+ * <p>
+ * This interface is not intended to be implemented by clients.
+ * </p>
+ * @since 3.1
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface PlatformAdmin {
+
+ /**
+ * Returns a mutable state representing the current system.
+ * <p>
+ * This is a convenience method, fully equivalent to
+ * <code>getState(true)</code>.
+ * </p>
+ * @return a state representing the current framework.
+ */
+ public State getState();
+
+ /**
+ * Returns a state representing the current system. If there is need to make
+ * changes to the returned state, a mutable state must be requested.
+ * Otherwise, an immutable state should be requested. In this case, invoking
+ * any of the operations that could cause the state to be changed will throw
+ * an <code>java.lang.UnsupportedOperationException</code>.
+ * <p>
+ * If a mutable state is requested, the resulting state will <strong>not</strong>
+ * be resolved and the user objects from the system state bundle descriptions will
+ * not be copied.
+ * </p>
+ * @param mutable whether the returned state should mutable
+ * @return a state representing the current framework.
+ */
+ public State getState(boolean mutable);
+
+ /**
+ * Returns a state helper object. State helpers provide convenience methods
+ * for manipulating states.
+ * <p>
+ * A possible implementation for this
+ * method would provide the same single StateHelper instance to all clients.
+ * </p>
+ *
+ * @return a state helper
+ * @see StateHelper
+ */
+ public StateHelper getStateHelper();
+
+ /**
+ * Commit the differences between the current state and the given state.
+ * The given state must return true from State.isResolved() or an exception
+ * is thrown. The resolved state is committed verbatim, as-is.
+ *
+ * @param state the future state of the framework
+ * @throws BundleException if the id of the given state does not match that of the
+ * current state or if the given state is not resolved.
+ */
+ public void commit(State state) throws BundleException;
+
+ /**
+ * Returns a resolver supplied by the system. The returned resolver
+ * will not be associated with any state.
+ * @return a system resolver
+ * @deprecated in favour of {@link #createResolver()}.
+ */
+ public Resolver getResolver();
+
+ /**
+ * Creates a new {@link Resolver} that is not associated with any {@link State}.
+ * @return the new <code>Resolver</code>.
+ * @since 3.5
+ */
+ public Resolver createResolver();
+
+ /**
+ * Returns a factory that knows how to create state objects, such as bundle
+ * descriptions and the different types of version constraints.
+ * @return a state object factory
+ */
+ public StateObjectFactory getFactory();
+
+ /**
+ * Adds the disabled info to the state managed by this platform admin.
+ * If a disable info already exists for the specified policy and the specified bundle
+ * then it is replaced with the given disabled info.
+ * @param disabledInfo the disabled info to add.
+ * @throws IllegalArgumentException if the <code>BundleDescription</code> for
+ * the specified disabled info does not exist in the state managed by this platform admin.
+ * @since 3.4
+ */
+ public void addDisabledInfo(DisabledInfo disabledInfo);
+
+ /**
+ * Removes the disabled info from the state managed by this platform admin.
+ * @param disabledInfo the disabled info to remove
+ * @since 3.4
+ */
+ public void removeDisabledInfo(DisabledInfo disabledInfo);
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/Resolver.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/Resolver.java
new file mode 100644
index 000000000..24bed1c85
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/Resolver.java
@@ -0,0 +1,146 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.service.resolver;
+
+import java.util.Comparator;
+import java.util.Dictionary;
+
+/**
+ * An implementation of a resolver which resolves the constraints of the bundles
+ * in a system.
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ * @since 3.1
+ */
+public interface Resolver {
+
+ /**
+ * Resolves the state associated with this resolver and returns an array of
+ * bundle deltas describing the changes.. The state and version bindings
+ * for the various bundles and packages in this state are updated and a
+ * array containing bundle deltas describing the changes returned.
+ * <p>
+ * This method is intended to be called only by State objects in response
+ * to a user invocation of State.resolve(). States will typically refuse to
+ * update their constituents (see State.resolveBundle() and
+ * State.resolveConstraint()) if their resolve method is not currently
+ * being invoked.
+ * </p>
+ * <p>
+ * Note the given state is destructively modified to reflect the results of
+ * resolution.
+ * </p>
+ * @param discard the list of bundles to discard the resolve status and
+ * reresolve. A <tt>null</tt> value indicates that all currently unresolved
+ * bundles in the state should be resolved.
+ * @param platformProperties the platform properties used to match platform filters
+ * against. A <tt>null</tt> value indicates that the system properties should
+ * be used to match against
+ */
+ public void resolve(BundleDescription[] discard, Dictionary<Object, Object>[] platformProperties);
+
+ /**
+ * Flushes this resolver of any stored/cached data it may be keeping to
+ * facilitate incremental processing on its associated state. This is
+ * typicaly used when switching the resolver's state object.
+ */
+ public void flush();
+
+ /**
+ * Returns the state associated with this resolver. A state can work with
+ * at most one resolver at any given time. Similarly, a resolver can work
+ * with at most one state at a time.
+ *
+ * @return the state for this resolver. null is returned if the resolver
+ * does not have a state
+ */
+ public State getState();
+
+ /**
+ * Sets the state associated with this resolver. A state can work with at
+ * most one resolver at any given time. Similarly, a resolver can work with
+ * at most one state at a time.
+ * <p>
+ * To ensure that this resolver and the given state are properly linked,
+ * the following expression must be included in this method if the given
+ * state (value) is not identical to the result of this.getState().
+ * </p>
+ *
+ * <pre>
+ * if (this.getState() != value) value.setResolver(this);
+ * </pre>
+ */
+ public void setState(State value);
+
+ /**
+ * Notifies the resolver a bundle has been added to the state.
+ * @param bundle
+ */
+ public void bundleAdded(BundleDescription bundle);
+
+ /**
+ * Notifies the resolver a bundle has been removed from the state.
+ * @param bundle the bundle description to remove
+ * @param pending indicates if the bundle to be remove has current dependents and
+ * will pend complete removal until the bundle has been re-resolved.
+ */
+ public void bundleRemoved(BundleDescription bundle, boolean pending);
+
+ /**
+ * Notifies the resolver a bundle has been updated in the state.
+ * @param newDescription the new description
+ * @param existingDescription the existing description
+ * @param pending indicates if the bundle to be updated has current dependents and
+ * will pend complete removal until the bundle has been re-resolved.
+ */
+ public void bundleUpdated(BundleDescription newDescription, BundleDescription existingDescription, boolean pending);
+
+ /**
+ * Attempts to find an ExportPackageDescription that will satisfy a dynamic import
+ * for the specified requestedPackage for the specified importingBundle. If no
+ * ExportPackageDescription is available that satisfies a dynamic import for the
+ * importingBundle then <code>null</code> is returned.
+ * @param importingBundle the BundleDescription that is requesting a dynamic package
+ * @param requestedPackage the name of the package that is being requested
+ * @return the ExportPackageDescription that satisfies the dynamic import request;
+ * a value of <code>null</code> is returned if none is available.
+ */
+ public ExportPackageDescription resolveDynamicImport(BundleDescription importingBundle, String requestedPackage);
+
+ /**
+ * Sets the selection policy for this resolver. A selection policy is used to sort
+ * possible suppliers of a version constraint in descending order. That is an order
+ * which is from most desired to least desired. The objects passed to the
+ * selection policy {@link Comparator#compare(Object, Object)} method
+ * will be of type {@link BaseDescription}. The selection policy should return a
+ * negative number, zero, or a positive number depending on if the first object is
+ * more desired, equal amount of desire, or less desired than the second object respectively.
+ * <p>
+ * If no selection policy is set then a default policy will be used which sorts according
+ * to the following rules:
+ * <ol>
+ * <li> The resolution status of the bundle which supplies the base description. Resolved bundles take priority over unresolved ones.
+ * <li> The version of the base description. Higher versions take priority over lower versions.
+ * <li> The bundle ID which supplies the base description. Lower IDs take priority over higher IDs.
+ * </ol>
+ * @param selectionPolicy the selection policy for this resolver
+ * @since 3.2
+ */
+ public void setSelectionPolicy(Comparator<BaseDescription> selectionPolicy);
+
+ /**
+ * Returns the selection policy for this resolver or null if it is not set
+ * @return the selection policy for this resolver or null if it is not set
+ * @since 3.2
+ */
+ public Comparator<BaseDescription> getSelectionPolicy();
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/ResolverError.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/ResolverError.java
new file mode 100644
index 000000000..ee823eefc
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/ResolverError.java
@@ -0,0 +1,194 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.service.resolver;
+
+/**
+ * ResolverErrors represent a single error that prevents a bundle from resolving
+ * in a <code>State</code> object.
+ * * <p>
+ * This interface is not intended to be implemented by clients.
+ * </p>
+ * @since 3.2
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface ResolverError {
+ /**
+ * Error type constant (bit mask) indicating that an Import-Package could
+ * not be resolved.
+ * @see ResolverError#getType()
+ */
+ public static final int MISSING_IMPORT_PACKAGE = 0x0001;
+ /**
+ * Error type constant (bit mask) indicating that a Require-Bundle could
+ * not be resolved.
+ * @see ResolverError#getType()
+ */
+ public static final int MISSING_REQUIRE_BUNDLE = 0x0002;
+ /**
+ * Error type constant (bit mask) indicating that a Fragment-Host could
+ * not be resolved.
+ * @see ResolverError#getType()
+ */
+ public static final int MISSING_FRAGMENT_HOST = 0x0004;
+ /**
+ * Error type constant (bit mask) indicating that the bundle could not
+ * be resolved because another singleton bundle was selected.
+ * @see ResolverError#getType()
+ */
+ public static final int SINGLETON_SELECTION = 0x0008;
+ /**
+ * Error type constant (bit mask) indicating that the bundle fragment
+ * could not be resolved because a constraint conflict with a host.
+ * @see ResolverError#getType()
+ */
+ public static final int FRAGMENT_CONFLICT = 0x0010;
+ /**
+ * Error type constant (bit mask) indicating that an Import-Package could
+ * not be resolved because of a uses directive conflict.
+ * @see ResolverError#getType()
+ */
+ public static final int IMPORT_PACKAGE_USES_CONFLICT = 0x0020;
+ /**
+ * Error type constant (bit mask) indicating that a Require-Bundle could
+ * not be resolved because of a uses directive conflict.
+ * @see ResolverError#getType()
+ */
+ public static final int REQUIRE_BUNDLE_USES_CONFLICT = 0x0040;
+ /**
+ * Error type constant (bit mask) indicating that an Import-Package could
+ * not be resolved because the importing bundle does not have the correct
+ * permissions to import the package.
+ * @see ResolverError#getType()
+ */
+ public static final int IMPORT_PACKAGE_PERMISSION = 0x0080;
+ /**
+ * Error type constant (bit mask) indicating that an Import-Package could
+ * not be resolved because no exporting bundle has the correct
+ * permissions to export the package.
+ * @see ResolverError#getType()
+ */
+ public static final int EXPORT_PACKAGE_PERMISSION = 0x0100;
+ /**
+ * Error type constant (bit mask) indicating that a Require-Bundle could
+ * not be resolved because the requiring bundle does not have the correct
+ * permissions to require the bundle.
+ * @see ResolverError#getType()
+ */
+ public static final int REQUIRE_BUNDLE_PERMISSION = 0x0200;
+ /**
+ * Error type constant (bit mask) indicating that a Require-Bundle could
+ * not be resolved because no bundle with the required symbolic name has
+ * the correct permissions to provide the required symbolic name.
+ * @see ResolverError#getType()
+ */
+ public static final int PROVIDE_BUNDLE_PERMISSION = 0x0400;
+ /**
+ * Error type constant (bit mask) indicating that a Fragment-Host could
+ * not be resolved because no bundle with the required symbolic name has
+ * the correct permissions to host a fragment.
+ * @see ResolverError#getType()
+ */
+ public static final int HOST_BUNDLE_PERMISSION = 0x0800;
+ /**
+ * Error type constant (bit mask) indicating that a Fragment-Host could
+ * not be resolved because the fragment bundle does not have the correct
+ * permissions to be a fragment.
+ * @see ResolverError#getType()
+ */
+ public static final int FRAGMENT_BUNDLE_PERMISSION = 0x1000;
+ /**
+ * Error type constant (bit mask) indicating that a bundle could not be
+ * resolved because a platform filter did not match the runtime environment.
+ * @see ResolverError#getType()
+ */
+ public static final int PLATFORM_FILTER = 0x2000;
+
+ /**
+ * Error type constant (bit mask) indicating that a bundle could not be
+ * resolved because the required execution enviroment did not match the runtime
+ * environment.
+ * @see ResolverError#getType()
+ */
+ public static final int MISSING_EXECUTION_ENVIRONMENT = 0x4000;
+
+ /**
+ * Error type constant (bit mask) indicating that a bundle could not be
+ * resolved because the required generic capability could not be resolved.
+ */
+ public static final int MISSING_GENERIC_CAPABILITY = 0x8000;
+
+ /**
+ * Error type constant (bit mask) indicating that a bundle could not be
+ * resolved because no match was found for the native code specification.
+ * @since 3.4
+ */
+ public static final int NO_NATIVECODE_MATCH = 0x10000;
+
+ /**
+ * Error type constant (bit mask) indicating that a bundle could not be
+ * resolved because the matching native code paths are invalid.
+ * @since 3.4
+ */
+ public static final int INVALID_NATIVECODE_PATHS = 0x20000;
+
+ /**
+ * Error type constant (bit mask) indicating that a bundle could not be
+ * resolved because the bundle was disabled
+ * @since 3.4
+ */
+ public static final int DISABLED_BUNDLE = 0x40000;
+
+ /**
+ * Error type constant (bit mask) indicating that a Require-Capability could
+ * not be resolved because the requiring bundle does not have the correct
+ * permissions to require the capability.
+ * @see ResolverError#getType()
+ * @since 3.7
+ */
+ public static final int REQUIRE_CAPABILITY_PERMISSION = 0x80000;
+
+ /**
+ * Error type constant (bit mask) indicating that a Require-Bundle could
+ * not be resolved because no bundle with the required symbolic name has
+ * the correct permissions to provide the required symbolic name.
+ * @see ResolverError#getType()
+ * @since 3.7
+ */
+ public static final int PROVIDE_CAPABILITY_PERMISSION = 0x100000;
+
+ /**
+ * Returns the bundle which this ResolverError is for
+ * @return the bundle which this ResolverError is for
+ */
+ public BundleDescription getBundle();
+
+ /**
+ * Returns the type of ResolverError this is
+ * @return the type of ResolverError this is
+ */
+ public int getType();
+
+ /**
+ * Returns non-translatable data associated with this ResolverError.
+ * For example, the data for a ResolverError of type MISSING_IMPORT_PACKAGE
+ * could be the Import-Package manifest statement which did not resolve.
+ * @return non-translatable data associated with this ResolverError
+ */
+ public String getData();
+
+ /**
+ * Returns the unsatisfied constraint if this ResolverError occurred
+ * because of an unsatisfied constraint; otherwise <code>null</code>
+ * is returned.
+ * @return the unsatisfied constraint or <code>null</code>.
+ */
+ public VersionConstraint getUnsatisfiedConstraint();
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/ResolverHookException.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/ResolverHookException.java
new file mode 100644
index 000000000..3feb596ac
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/ResolverHookException.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.service.resolver;
+
+/**
+ * A runtime exception thrown by a resolver to indicate that a resolver
+ * hook threw an unexpected exception and the resolve operation terminated.
+ * @since 3.7
+ */
+public class ResolverHookException extends RuntimeException {
+ private static final long serialVersionUID = 5686047743173396286L;
+
+ /**
+ * Constructs a new resolver hook exception.
+ * @param message the message of the exception
+ * @param cause the cause of the exception
+ */
+ public ResolverHookException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/State.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/State.java
new file mode 100644
index 000000000..ab13c3fd1
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/State.java
@@ -0,0 +1,638 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.service.resolver;
+
+import java.util.*;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Version;
+import org.osgi.framework.hooks.resolver.ResolverHookFactory;
+
+/**
+ * The state of a system as reported by a resolver. This includes all bundles
+ * presented to the resolver relative to this state (i.e., both resolved and
+ * unresolved).
+ * <p>
+ * This interface is not intended to be implemented by clients. The
+ * {@link StateObjectFactory} should be used to construct instances.
+ * </p>
+ * @since 3.1
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface State {
+ /**
+ * Adds the given bundle to this state.
+ * <p>
+ * If the bundle already exists in another state then an <code>IllegalStateException</code>
+ * will be thrown. Note that even if you remove a <code>BundleDescription</code> from
+ * one <code>State</code> object using {@link State#removeBundle(BundleDescription)} it
+ * may still be considered as removing pending if other bundles in that state depend on the
+ * bundle you removed. To complete a pending removal a call must be done to
+ * {@link State#resolve(BundleDescription[])} with the removed bundle.
+ * </p>
+ *
+ * @param description the description to add
+ * @return a boolean indicating whether the bundle was successfully added
+ * @throws IllegalStateException if the bundle already exists in another state
+ */
+ public boolean addBundle(BundleDescription description);
+
+ /**
+ * Returns a delta describing the differences between this state and the
+ * given state. The given state is taken as the base so the absence of a bundle
+ * in this state is reported as a deletion, etc.
+ *<p>Note that the generated StateDelta will contain BundleDeltas with one
+ *of the following types: BundleDelta.ADDED, BundleDelta.REMOVED and
+ *BundleDelta.UPDATED</p>
+ *
+ * @param baseState the base state
+ * @return a delta describing differences between this and the base state state
+ */
+ public StateDelta compare(State baseState) throws BundleException;
+
+ /**
+ * Removes a bundle description with the given bundle id.
+ *
+ * @param bundleId the id of the bundle description to be removed
+ * @return the removed bundle description, or <code>null</code>, if a bundle
+ * with the given id does not exist in this state
+ */
+ public BundleDescription removeBundle(long bundleId);
+
+ /**
+ * Removes the given bundle description.
+ *
+ * @param bundle the bundle description to be removed
+ * @return <code>true</code>, if if the bundle description was removed,
+ * <code>false</code> otherwise
+ */
+ public boolean removeBundle(BundleDescription bundle);
+
+ /**
+ * Updates an existing bundle description with the given description.
+ *
+ * @param newDescription the bundle description to replace an existing one
+ * @return <code>true</code>, if if the bundle description was updated,
+ * <code>false</code> otherwise
+ */
+ public boolean updateBundle(BundleDescription newDescription);
+
+ /**
+ * Returns the delta representing the changes from the time this state was
+ * first captured until now.
+ *
+ * @return the state delta
+ */
+ public StateDelta getChanges();
+
+ /**
+ * Returns descriptions for all bundles known to this state.
+ *
+ * @return the descriptions for all bundles known to this state.
+ */
+ public BundleDescription[] getBundles();
+
+ /**
+ * Returns the bundle descriptor for the bundle with the given id.
+ * <code>null</code> is returned if no such bundle is found in
+ * this state.
+ *
+ * @return the descriptor for the identified bundle
+ * @see BundleDescription#getBundleId()
+ */
+ public BundleDescription getBundle(long id);
+
+ /**
+ * Returns the bundle descriptor for the bundle with the given name and
+ * version. A null value is returned if no such bundle is found in this state.
+ * A resolved bundle is always preferably returned over an unresolved bundle.
+ * If multiple bundles with the same resolution state are available, the bundle
+ * with the highest version number is returned if the <code>version<code> is
+ * null.
+ *
+ * @param symbolicName symbolic name of the bundle to query
+ * @param version version of the bundle to query. null matches any bundle
+ * @return the descriptor for the identified bundle
+ */
+ public BundleDescription getBundle(String symbolicName, Version version);
+
+ /**
+ * Returns the bundle descriptor for the bundle with the given location
+ * identifier. null is returned if no such bundle is found in this state.
+ *
+ * @param location location identifier of the bundle to query
+ * @return the descriptor for the identified bundle
+ */
+ public BundleDescription getBundleByLocation(String location);
+
+ /**
+ * Returns the timestamp for this state. This
+ * correlates this timestamp to the system state. For example, if
+ * the system state timestamp is 4 but then some bundles are installed,
+ * the system state timestamp is updated. By comparing 4 to the current system
+ * state timestamp it is possible to detect if the states are out of sync.
+ *
+ * @return the timestamp of this state
+ */
+ public long getTimeStamp();
+
+ /**
+ * Sets the timestamp for this state
+ * @param newTimeStamp the new timestamp for this state
+ */
+ public void setTimeStamp(long newTimeStamp);
+
+ /**
+ * Returns true if there have been no modifications to this state since the
+ * last time resolve() was called.
+ *
+ * @return whether or not this state has changed since last resolved.
+ */
+ public boolean isResolved();
+
+ /**
+ * Resolves the given version constraint with the given supplier. The given
+ * constraint object is destructively modified to reflect its new resolved
+ * state. Note that a constraint can be unresolved by passing null for
+ * the supplier.
+ * <p>
+ * This method is intended to be used by resolvers in the process of
+ * determining which constraints are satisfied by which components.
+ * </p>
+ *
+ * @param constraint the version constraint to update
+ * @param supplier the supplier which satisfies the constraint. May be null if
+ * the constraint is to be unresolved.
+ * @throws IllegalStateException if this is not done during a call to
+ * <code>resolve</code>
+ */
+ public void resolveConstraint(VersionConstraint constraint, BaseDescription supplier);
+
+ /**
+ * Sets whether or not the given bundle is selected in this state.
+ * <p>
+ * This method is intended to be used by resolvers in the process of
+ * determining which constraints are satisfied by which components.
+ * </p>
+ *
+ * @param bundle the bundle to update
+ * @param status whether or not the given bundle is resolved, if false the other parameters are ignored
+ * @param hosts the host for the resolve fragment, can be <code>null</code>
+ * @param selectedExports the selected exported packages for this resolved bundle, can be <code>null</code>
+ * @param resolvedRequires the {@link BundleDescription}s that resolve the required bundles for this bundle, can be <code>null</code>
+ * @param resolvedImports the exported packages that resolve the imports for this bundle, can be <code>null</code>
+ * @throws IllegalStateException if this is not done during a call to <code>resolve</code>
+ * @deprecated use {@link #resolveBundle(BundleDescription, boolean, BundleDescription[], ExportPackageDescription[], ExportPackageDescription[], GenericDescription[], BundleDescription[], ExportPackageDescription[], GenericDescription[], Map)}
+ */
+ public void resolveBundle(BundleDescription bundle, boolean status, BundleDescription[] hosts, ExportPackageDescription[] selectedExports, BundleDescription[] resolvedRequires, ExportPackageDescription[] resolvedImports);
+
+ /**
+ * Sets whether or not the given bundle is selected in this state.
+ * <p>
+ * This method is intended to be used by resolvers in the process of
+ * determining which constraints are satisfied by which components.
+ * </p>
+ *
+ * @param bundle the bundle to update
+ * @param status whether or not the given bundle is resolved, if false the other parameters are ignored
+ * @param hosts the host for the resolve fragment, can be <code>null</code>
+ * @param selectedExports the selected exported packages for this resolved bundle, can be <code>null</code>
+ * @param substitutedExports the exported packages that resolve imports for this bundle and substitute exports, can be <code>null</code>
+ * @param resolvedRequires the {@link BundleDescription}s that resolve the required bundles for this bundle, can be <code>null</code>
+ * @param resolvedImports the exported packages that resolve the imports for this bundle, can be <code>null</code>
+ * @throws IllegalStateException if this is not done during a call to <code>resolve</code>
+ * @since 3.4
+ * @deprecated use {@link #resolveBundle(BundleDescription, boolean, BundleDescription[], ExportPackageDescription[], ExportPackageDescription[], GenericDescription[], BundleDescription[], ExportPackageDescription[], GenericDescription[], Map)}
+ */
+ public void resolveBundle(BundleDescription bundle, boolean status, BundleDescription[] hosts, ExportPackageDescription[] selectedExports, ExportPackageDescription[] substitutedExports, BundleDescription[] resolvedRequires, ExportPackageDescription[] resolvedImports);
+
+ /**
+ * Sets whether or not the given bundle is selected in this state.
+ * <p>
+ * This method is intended to be used by resolvers in the process of
+ * determining which constraints are satisfied by which components.
+ * </p>
+ *
+ * @param bundle the bundle to update
+ * @param status whether or not the given bundle is resolved, if false the other parameters are ignored
+ * @param hosts the host for the resolve fragment, can be <code>null</code>
+ * @param selectedExports the selected exported packages for this resolved bundle, can be <code>null</code>
+ * @param substitutedExports the exported packages that resolve imports for this bundle and substitute exports, can be <code>null</code>
+ * @param selectedCapabilities the selected capabilities for this resolved bundle, can be <code>null</code>
+ * @param resolvedRequires the {@link BundleDescription}s that resolve the required bundles for this bundle, can be <code>null</code>
+ * @param resolvedImports the exported packages that resolve the imports for this bundle, can be <code>null</code>
+ * @param resolvedCapabilities the capabilities that resolve the required capabilities for this bundle, can be <code>null</code>
+ * @param resolvedWires the map of state wires for the resolved requirements of the given bundle. The key is the name space of the requirement.
+ * @throws IllegalStateException if this is not done during a call to <code>resolve</code>
+ * @since 3.7
+ */
+ public void resolveBundle(BundleDescription bundle, boolean status, BundleDescription[] hosts, ExportPackageDescription[] selectedExports, ExportPackageDescription[] substitutedExports, GenericDescription[] selectedCapabilities, BundleDescription[] resolvedRequires, ExportPackageDescription[] resolvedImports, GenericDescription[] resolvedCapabilities, Map<String, List<StateWire>> resolvedWires);
+
+ /**
+ * Sets the given removal pending bundle to removal complete for this state.
+ * <p>
+ * This method is intended to be used by resolvers in the process of
+ * resolving bundles.
+ * </p>
+ * @param bundle the bundle to set a removal complete.
+ * @throws IllegalStateException if this is not done during a call to
+ * <code>resolve</code>
+ */
+ public void removeBundleComplete(BundleDescription bundle);
+
+ /**
+ * Adds a new <code>ResolverError</code> for the specified bundle.
+ * <p>
+ * This method is intended to be used by resolvers in the process of
+ * resolving.
+ * </p>
+ *
+ * @param bundle the bundle to add a new <code>ResolverError</code> for
+ * @param type the type of <code>ResolverError</code> to add
+ * @param data the data for the <code>ResolverError</code>
+ * @param unsatisfied the unsatisfied constraint or null if the resolver error was not caused
+ * by an unsatisfied constraint.
+ * @throws IllegalStateException if this is not done during a call to <code>resolve</code>
+ * @since 3.2
+ */
+ public void addResolverError(BundleDescription bundle, int type, String data, VersionConstraint unsatisfied);
+
+ /**
+ * Removes all <code>ResolverError</code>s for the specified bundle.
+ * <p>
+ * This method is intended to be used by resolvers in the process of
+ * resolving.
+ * </p>
+ *
+ * @param bundle the bundle to remove all <code>ResolverError</code>s for
+ * @throws IllegalStateException if this is not done during a call to <code>resolve</code>
+ * @since 3.2
+ */
+ public void removeResolverErrors(BundleDescription bundle);
+
+ /**
+ * Returns all <code>ResolverError</code>s for the given bundle
+ * @param bundle the bundle to get all <code>ResolverError</code>s for
+ * @return all <code>ResolverError</code>s for the given bundle
+ * @since 3.2
+ */
+ public ResolverError[] getResolverErrors(BundleDescription bundle);
+
+ /**
+ * Returns the resolver associated with this state. A state can work with
+ * at most one resolver at any given time. Similarly, a resolver can work
+ * with at most one state at a time.
+ *
+ * @return the resolver for this state. null is returned if the state does
+ * not have a resolver
+ */
+ public Resolver getResolver();
+
+ /**
+ * Sets the resolver associated with this state. A state can work with at
+ * most one resolver at any given time. Similarly, a resolver can work with
+ * at most one state at a time.
+ * <p>
+ * To ensure that this state and the given resovler are properly linked,
+ * the following expression must be included in this method if the given
+ * resolver (value) is not identical to the result of this.getResolver().
+ *
+ * <pre>
+ * if (this.getResolver() != value) value.setState(this);
+ * </pre>
+ *
+ * </p>
+ */
+ // TODO what happens if you set the Resolver after some bundles have
+ // been added to the state but it is not resolved? Should setting
+ // the resolver force a state to be unresolved?
+ public void setResolver(Resolver value);
+
+ /**
+ * Resolves the constraints contained in this state using the resolver
+ * currently associated with the state and returns a delta describing the
+ * changes in resolved states and dependencies in the state.
+ * <p>
+ * Note that this method is typically implemented using
+ *
+ * <pre>
+ * this.getResolver().resolve();
+ * </pre>
+ *
+ * and is the preferred path for invoking resolution. In particular, states
+ * should refuse to perform updates (@see #select() and
+ * #resolveConstraint()) if they are not currently involved in a resolution
+ * cycle.
+ * <p>
+ * Note the given state is destructively modified to reflect the results of
+ * resolution.
+ * </p>
+ *
+ * @param incremental a flag controlling whether resolution should be incremental
+ * @return a delta describing the changes in resolved state and
+ * interconnections
+ */
+ public StateDelta resolve(boolean incremental);
+
+ /**
+ * Same as State.resolve(true);
+ */
+ public StateDelta resolve();
+
+ /**
+ * Resolves the constraints contained in this state using the resolver
+ * currently associated with the state in an incremental, "least-perturbing"
+ * mode, and returns a delta describing the changes in resolved states and
+ * dependencies in the state.
+ *
+ * @param discard an array containing descriptions for bundles whose
+ * current resolution state should be forgotten. If <code>null</code>
+ * then all the current removal pending BundleDescriptions are refreshed.
+ * @return a delta describing the changes in resolved state and
+ * interconnections
+ */
+ public StateDelta resolve(BundleDescription[] discard);
+
+ /**
+ * Resolves the constraints contained in this state using the resolver
+ * currently associated with the state in an incremental, "least-perturbing"
+ * mode, and returns a delta describing the changes in resolved states and
+ * dependencies in the state. If discard is set to true the
+ * the descriptions contained in the resolve array will have their
+ * current resolution state discarded and will be re-resolved.
+ * This method will attempt to resolve the supplied descriptions
+ * and may attempt to resolve any other unresolved descriptions contained
+ * in this state.
+ *
+ * @param resolve an array containing descriptions for bundles to resolve.
+ * @param discard a value of true indicates the resolve descriptions
+ * should have their current resolution state discarded and re-resolved.
+ * @return a delta describing the changes in the resolved state and
+ * interconnections.
+ * @since 3.7
+ */
+ public StateDelta resolve(BundleDescription[] resolve, boolean discard);
+
+ /**
+ * Sets the version overrides which are to be applied during the resolutoin
+ * of this state. Version overrides allow external forces to
+ * refine/override the version constraints setup by the components in the
+ * state.
+ *
+ * @param value
+ * @deprecated The exact form of this has never been defined. There is
+ * no alternative method available.
+ */
+ public void setOverrides(Object value);
+
+ /**
+ * Returns descriptions for all bundles currently resolved in this state.
+ *
+ * @return the descriptions for all bundles currently resolved in this
+ * state.
+ */
+ public BundleDescription[] getResolvedBundles();
+
+ /**
+ * Returns descriptions for all bundles in a removal pending state.
+ * @return the descriptions for all bundles in a removal pending state.
+ * @since 3.7
+ */
+ public BundleDescription[] getRemovalPending();
+
+ /**
+ * Returns the dependency closure for the specified bundles.
+ *
+ * <p>
+ * A graph of bundles is computed starting with the specified bundles. The
+ * graph is expanded by adding any bundle that is either wired to a package
+ * that is currently exported by a bundle in the graph or requires a bundle
+ * in the graph. The graph is fully constructed when there is no bundle
+ * outside the graph that is wired to a bundle in the graph. The graph may
+ * contain removal pending bundles.
+ *
+ * @param bundles The initial bundles for which to generate the dependency
+ * closure.
+ * @return A collection containing a snapshot of the dependency closure of
+ * the specified bundles, or an empty collection if there were no
+ * specified bundles.
+ * @since 3.7
+ */
+ public Collection<BundleDescription> getDependencyClosure(Collection<BundleDescription> bundles);
+
+ /**
+ * Returns whether this state is empty.
+ * @return <code>true</code> if this state is empty, <code>false</code>
+ * otherwise
+ */
+ public boolean isEmpty();
+
+ /**
+ * Returns all exported packages in this state, according to the OSGi rules for resolution.
+ * @see org.osgi.service.packageadmin.PackageAdmin#getExportedPackages(org.osgi.framework.Bundle)
+ */
+ public ExportPackageDescription[] getExportedPackages();
+
+ /**
+ * Returns all bundle descriptions with the given bundle symbolic name.
+ * @param symbolicName symbolic name of the bundles to query
+ * @return the descriptors for all bundles known to this state with the
+ * specified symbolic name.
+ */
+ public BundleDescription[] getBundles(String symbolicName);
+
+ /**
+ * Returns the factory that created this state.
+ * @return the state object factory that created this state
+ */
+ public StateObjectFactory getFactory();
+
+ /**
+ * Attempts to find an ExportPackageDescription that will satisfy a dynamic import
+ * for the specified requestedPackage for the specified importingBundle. If no
+ * ExportPackageDescription is available that satisfies a dynamic import for the
+ * importingBundle then <code>null</code> is returned.
+ * @param importingBundle the BundleDescription that is requesting a dynamic package
+ * @param requestedPackage the name of the package that is being requested
+ * @return the ExportPackageDescription that satisfies the dynamic import request;
+ * a value of <code>null</code> is returned if none is available.
+ */
+ public ExportPackageDescription linkDynamicImport(BundleDescription importingBundle, String requestedPackage);
+
+ /**
+ * Adds the specified dynamic imports to the specified importingBundle. The added
+ * dynamic imports are only valid for the instance of this state and will be
+ * forgotten if this state is read from a persistent cache.
+ * @param importingBundle the bundle to add the imports to.
+ * @param dynamicImports the dynamic imports to add.
+ * @since 3.7
+ * @see BundleDescription#getAddedDynamicImportPackages()
+ */
+ public void addDynamicImportPackages(BundleDescription importingBundle, ImportPackageSpecification[] dynamicImports);
+
+ /**
+ * Sets the platform properties of the state. The platform properties
+ * are used to resolve the following constraints:
+ * <ul>
+ * <li> The execution environment requirements (i.e. Bundle-RequiredExecutionEnvironment).</li>
+ * <li>The platform filter requirements (i.e. Eclipse-PlatformFilter).</li>
+ * <li>The native code requirements (i.e. Bundle-NativeCode).</li>
+ * </ul>
+ * Arbitrary keys may be used in the platform properties but the following keys have a specified meaning:
+ * <ul>
+ * <li>osgi.nl - the platform language setting.</li>
+ * <li>osgi.os - the platform operating system.</li>
+ * <li>osgi.arch - the platform architecture.</li>
+ * <li>osgi.ws - the platform windowing system.</li>
+ * <li>osgi.resolverMode - the resolver mode. A value of "strict" will set the resolver mode to strict.</li>
+ * <li>org.osgi.framework.system.packages - the packages exported by the system bundle.</li>
+ * <li>org.osgi.framework.executionenvironment - the comma separated list of supported execution environments.
+ * This property is then used to resolve the required execution environment the bundles in a state.</li>
+ * <li>org.osgi.framework.os.name - the name of the operating system. This property is used to resolve the osname attribute of
+ * bundle native code (i.e. Bundle-NativeCode).</li>
+ * <li>org.osgi.framework.os.version - the version of the operating system. This property is used to resolve the osversion attribute
+ * of bundle native code (i.e. Bundle-NativeCode).</li>
+ * <li>org.osgi.framework.processor - the processor name. This property is used to resolve the processor attribute
+ * of bundle native code (i.e. Bundle-NativeCode).</li>
+ * <li>org.osgi.framework.language - the language being used. This property is used to resolve the language attribute
+ * of bundle native code (i.e. Bundle-NativeCode).</li>
+ * </ul>
+ * <p>
+ * The values used for the supported properties can be <tt>String</tt> type
+ * to specify a single value for the property or they can by <tt>String[]</tt>
+ * to specify a list of values for the property.
+ * @param platformProperties the platform properties of the state
+ * @return false if the platformProperties specified do not change any of the
+ * supported properties already set. If any of the supported property values
+ * are changed as a result of calling this method then true is returned.
+ */
+ public boolean setPlatformProperties(Dictionary<?, ?> platformProperties);
+
+ /**
+ * Sets the platform properties of the state to a list of platform properties.
+ * @see #setPlatformProperties(Dictionary)
+ *
+ * @param platformProperties a set of platform properties for the state
+ * @return false if the platformProperties specified do not change any of the
+ * supported properties already set. If any of the supported property values
+ * are changed as a result of calling this method then true is returned.
+ */
+ public boolean setPlatformProperties(Dictionary<?, ?>[] platformProperties);
+
+ /**
+ * Returns the list of platform properties currently set for this state.
+ * @return the list of platform properties currently set for this state.
+ */
+ @SuppressWarnings("rawtypes")
+ public Dictionary[] getPlatformProperties();
+
+ /**
+ * Returns the list of system packages which are exported by the system bundle.
+ * The list of system packages is set by the org.osgi.framework.system.packages
+ * value in the platform properties for this state.
+ * @see #setPlatformProperties(Dictionary)
+ * @return the list of system packages
+ */
+ public ExportPackageDescription[] getSystemPackages();
+
+ /**
+ * Returns a state helper object. State helpers provide convenience methods
+ * for manipulating states.
+ * <p>
+ * A possible implementation for this
+ * method would provide the same single StateHelper instance to all clients.
+ * </p>
+ *
+ * @return a state helper
+ * @see StateHelper
+ * @since 3.2
+ */
+ public StateHelper getStateHelper();
+
+ /**
+ * Returns the highest bundle ID. The value -1 is returned if no
+ * bundles exist in this state.
+ * <p>
+ * Note that this method returns the highest bundle ID the ever existed in this
+ * this state object. This bundle may have been removed from the state.
+ * @return the highest bundle ID.
+ * @since 3.3
+ */
+ public long getHighestBundleId();
+
+ /**
+ * Sets the native code paths of a native code description as invalid. Native
+ * code paths are invalid if they can not be found in the bundle content.
+ * <p>
+ * The framework, or some other entity which has access to bundle content,
+ * will call this method to validate or invalidate native code paths.
+ * </p>
+ * @param nativeCodeDescription the native code description.
+ * @param hasInvalidNativePaths true if the native code paths are invalid; false otherwise.
+ * @since 3.4
+ */
+ public void setNativePathsInvalid(NativeCodeDescription nativeCodeDescription, boolean hasInvalidNativePaths);
+
+ /**
+ * Returns an array of BundleDescriptions for the bundles that are disabled
+ * in the system. Use {@link #getDisabledInfos(BundleDescription)} to interrogate the reason that
+ * each bundle is disabled.
+ * @return the array of disabled bundles. An empty array is returned if no bundles are disabled.
+ * @see DisabledInfo
+ * @since 3.4
+ */
+ public BundleDescription[] getDisabledBundles();
+
+ /**
+ * Adds the disabled info to this state. If a disable info already exists
+ * for the specified policy and the specified bundle then it is replaced with
+ * the given disabled info.
+ * @param disabledInfo the disabled info to add.
+ * @throws IllegalArgumentException if the <code>BundleDescription</code> for
+ * the specified disabled info does not exist in this state.
+ * @since 3.4
+ */
+ public void addDisabledInfo(DisabledInfo disabledInfo);
+
+ /**
+ * Removes the disabled info from the state.
+ * @param disabledInfo the disabled info to remove
+ * @since 3.4
+ */
+ public void removeDisabledInfo(DisabledInfo disabledInfo);
+
+ /**
+ * Returns an array of disabled info for the specified bundle. If no disabled info exist
+ * then an empty array is returned.
+ * @param bundle the bundle to get the disabled info for.
+ * @return the array of disabled info.
+ * @since 3.4
+ */
+ public DisabledInfo[] getDisabledInfos(BundleDescription bundle);
+
+ /**
+ * Returns the disabled info for the specified bundle with the specified policy name.
+ * If no disabled info exists then <code>null</code> is returned.
+ * @param bundle the bundle to get the disabled info for
+ * @return the disabled info.
+ * @since 3.4
+ */
+ public DisabledInfo getDisabledInfo(BundleDescription bundle, String policyName);
+
+ /**
+ * Sets the resolver hook factory for this state. The resolver hook factory is
+ * used during resolve operations according to the OSGi specification for the
+ * resolver hook factory.
+ * @param hookFactory the resolver hook factory
+ * @since 3.7
+ * @throws IllegalStateException if the resolver hook factory is already set
+ */
+ public void setResolverHookFactory(ResolverHookFactory hookFactory);
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/StateDelta.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/StateDelta.java
new file mode 100644
index 000000000..33690806b
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/StateDelta.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.service.resolver;
+
+/**
+ * A state delta contains all the changes to bundles within a state.
+ * <p>
+ * This interface is not intended to be implemented by clients.
+ * </p>
+ * @since 3.1
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface StateDelta {
+ /**
+ * Returns an array of all the bundle deltas in this delta regardless of type.
+ * @return an array of bundle deltas
+ */
+ public BundleDelta[] getChanges();
+
+ /**
+ * Returns an array of all the members
+ * of this delta which match the given flags. If an exact match is requested
+ * then only delta members whose type exactly matches the given mask are
+ * included. Otherwise, all bundle deltas whose type's bit-wise and with the
+ * mask is non-zero are included.
+ *
+ * @param mask
+ * @param exact
+ * @return an array of bundle deltas matching the given match criteria.
+ */
+ public BundleDelta[] getChanges(int mask, boolean exact);
+
+ /**
+ * Returns the state whose changes are represented by this delta.
+ * @return the state
+ */
+ public State getState();
+
+ /**
+ * Returns the resolver hook exception if one occurred while
+ * resolving the state.
+ * @since 3.7
+ */
+ public ResolverHookException getResovlerHookException();
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/StateHelper.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/StateHelper.java
new file mode 100644
index 000000000..a5156ebc1
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/StateHelper.java
@@ -0,0 +1,212 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2009 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.service.resolver;
+
+/**
+ * A helper class that provides convenience methods for manipulating
+ * state objects. <code>PlatformAdmin</code> provides an access point
+ * for a state helper.
+ * <p>
+ * This interface is not intended to be implemented by clients.
+ * </p>
+ * @since 3.1
+ * @see PlatformAdmin#getStateHelper
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface StateHelper {
+ /**
+ * Indicates that access is encouraged to an <code>ExportPackageDescription</code>.
+ */
+ public static int ACCESS_ENCOURAGED = 0x01;
+ /**
+ * Indicates that access is discouraged to an <code>ExportPackageDescription</code>.
+ */
+ public static int ACCESS_DISCOURAGED = 0x02;
+
+ /**
+ * An option to include packages available from the execution environment when
+ * getting the visible packages of a bundle. For example, when running on a
+ * J2SE 1.4 VM the system bundle will export the javax.xml.parsers package as part of the
+ * execution environment. When this option is used then any packages from the execution
+ * environment which the bundle is wired to will be included.
+ * @see StateHelper#getVisiblePackages(BundleDescription, int)
+ */
+ public static int VISIBLE_INCLUDE_EE_PACKAGES = 0x01;
+
+ /**
+ * An option to get all visible packages that a host bundle is currently wired to. This
+ * includes packages wired to as a result of a dynamic import and packages wired to as a
+ * result of additional constraints specified by a fragment bundle. Using this option
+ * with a fragment will cause an empty array to be returned.
+ * @see StateHelper#getVisiblePackages(BundleDescription, int)
+ * @since 3.6
+ */
+ public static int VISIBLE_INCLUDE_ALL_HOST_WIRES = 0x02;
+
+ /**
+ * Returns all bundles in the state depending on the given bundles. The given bundles
+ * appear in the returned array.
+ *
+ * @param bundles the initial set of bundles
+ * @return an array containing bundle descriptions for the given roots and all
+ * bundles in the state that depend on them
+ */
+ public BundleDescription[] getDependentBundles(BundleDescription[] bundles);
+
+ /**
+ * Returns all the prerequisite bundles in the state for the given bundles. The given
+ * bundles appear in the returned array.
+ * @param bundles the inital set of bundles
+ * @return an array containing bundle descriptions for the given leaves and their
+ * prerequisite bundles in the state.
+ * @since 3.2
+ */
+ public BundleDescription[] getPrerequisites(BundleDescription[] bundles);
+
+ /**
+ * Returns all unsatisfied constraints in the given bundle. Returns an
+ * empty array if no unsatisfied constraints can be found.
+ * <p>
+ * Note that a bundle may have no unsatisfied constraints and still not be
+ * resolved.
+ * </p>
+ *
+ * @param bundle the bundle to examine
+ * @return an array containing all unsatisfied constraints for the given bundle
+ */
+ public VersionConstraint[] getUnsatisfiedConstraints(BundleDescription bundle);
+
+ /**
+ * Returns all unsatisfied constraints in the given bundles that have no possible supplier.
+ * Returns an empty array if no unsatisfied leaf constraints can be found.
+ * <p>
+ * The returned constraints include only the unsatisfied constraints in the given
+ * state that have no possible supplier (leaf constraints). There may
+ * be additional unsatisfied constraints in the given bundles but these will have at
+ * least one possible supplier. In this case the possible supplier of the constraint
+ * is not resolved for some reason. For example, a given state only has Bundles X and Y
+ * installed and Bundles X and Y have the following constraints:
+ * </p>
+ * <pre>
+ * Bundle X requires Bundle Y
+ * Bundle Y requires Bundle Z</pre>
+ * <p>
+ * In this case Bundle Y has an unsatisfied constraint leaf on Bundle Z. This will
+ * cause Bundle X's constraint on Bundle Y to be unsatisfied as well because the
+ * bundles are involved in a dependency chain. Bundle X's constraint on Bundle Y is
+ * not considered a leaf because there is a possible supplier Y in the given state.
+ * </p>
+ * <p>
+ * Note that a bundle may have no unsatisfied constraints and still not be
+ * resolved.
+ * </p>
+ *
+ * @param bundles the bundles to examine
+ * @return an array containing all unsatisfied leaf constraints for the given bundles
+ * @since 3.2
+ */
+ public VersionConstraint[] getUnsatisfiedLeaves(BundleDescription[] bundles);
+
+ /**
+ * Returns whether the given package specification constraint is resolvable.
+ * A package specification constraint may be
+ * resolvable but not resolved, which means that the bundle that provides
+ * it has not been resolved for some other reason (e.g. another constraint
+ * could not be resolved, another version has been picked, etc).
+ *
+ * @param specification the package specification constraint to be examined
+ * @return <code>true</code> if the constraint can be resolved,
+ * <code>false</code> otherwise
+ */
+ public boolean isResolvable(ImportPackageSpecification specification);
+
+ /**
+ * Returns whether the given bundle specification constraint is resolvable.
+ * A bundle specification constraint may be
+ * resolvable but not resolved, which means that the bundle that provides
+ * it has not been resolved for some other reason (e.g. another constraint
+ * could not be resolved, another version has been picked, etc).
+ *
+ * @param specification the bundle specification constraint to be examined
+ * @return <code>true</code> if the constraint can be resolved,
+ * <code>false</code> otherwise
+ */
+ public boolean isResolvable(BundleSpecification specification);
+
+ /**
+ * Returns whether the given host specification constraint is resolvable.
+ * A host specification constraint may be
+ * resolvable but not resolved, which means that the bundle that provides
+ * it has not been resolved for some other reason (e.g. another constraint
+ * could not be resolved, another version has been picked, etc).
+ *
+ * @param specification the host specification constraint to be examined
+ * @return <code>true</code> if the constraint can be resolved,
+ * <code>false</code> otherwise
+ */
+ public boolean isResolvable(HostSpecification specification);
+
+ /**
+ * Sorts the given array of <strong>resolved</strong> bundles in pre-requisite order. If A
+ * requires B, A appears after B.
+ * Fragments will appear after all of their hosts. Constraints contributed by fragments will
+ * be treated as if contributed by theirs hosts, affecting their position. This is true even if
+ * the fragment does not appear in the given bundle array.
+ * <p>
+ * Unresolved bundles are ignored.
+ * </p>
+ *
+ * @param toSort an array of bundles to be sorted
+ * @return any cycles found
+ */
+ public Object[][] sortBundles(BundleDescription[] toSort);
+
+ /**
+ * Returns a list of all packages that the specified bundle has access to which are
+ * exported by other bundles.
+ * <p>
+ * Same as calling getVisiblePackages(bundle, 0)
+ * </p>
+ * @param bundle a bundle to get the list of packages for.
+ * @return a list of all packages that the specified bundle has access to which are
+ * exported by other bundles.
+ */
+ public ExportPackageDescription[] getVisiblePackages(BundleDescription bundle);
+
+ /**
+ * Returns a list of all packages that the specified bundle has access to which are
+ * exported by other bundles. This takes into account all constraint specifications
+ * (Import-Package, Require-Bundle etc). A deep dependency search is done for all
+ * packages which are available through the required bundles and any bundles which
+ * are reexported. This method also takes into account all directives
+ * which may be specified on the constraint specifications (e.g. uses, x-friends etc.)
+ *
+ * @param bundle a bundle to get the list of packages for.
+ * @param options the options for selecting the visible packages
+ * @return a list of all packages that the specified bundle has access to which are
+ * exported by other bundles.
+ * @see StateHelper#VISIBLE_INCLUDE_EE_PACKAGES
+ * @see StateHelper#VISIBLE_INCLUDE_ALL_HOST_WIRES
+ * @since 3.3
+ */
+ public ExportPackageDescription[] getVisiblePackages(BundleDescription bundle, int options);
+
+ /**
+ * Returns the access code that the specified <code>BundleDescription</code> has to the
+ * specified <code>ExportPackageDescription</code>.
+ * @param bundle the bundle to find the access code for
+ * @param export the export to find the access code for
+ * @return the access code to the export.
+ * @see StateHelper#ACCESS_ENCOURAGED
+ * @see StateHelper#ACCESS_DISCOURAGED
+ */
+ public int getAccessCode(BundleDescription bundle, ExportPackageDescription export);
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/StateObjectFactory.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/StateObjectFactory.java
new file mode 100644
index 000000000..c0f6701bc
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/StateObjectFactory.java
@@ -0,0 +1,487 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.service.resolver;
+
+import java.io.*;
+import java.util.*;
+import org.eclipse.osgi.internal.resolver.StateObjectFactoryImpl;
+import org.osgi.framework.*;
+
+/**
+ * A factory for states and their component objects.
+ * <p>
+ * This interface is not intended to be implemented by clients.
+ * </p>
+ * @since 3.1
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface StateObjectFactory {
+
+ /**
+ * The default object factory that can be used to create, populate and resolve
+ * states. This is particularly useful when using the resolver outside the context
+ * of a running Equinox framework.
+ */
+ public static final StateObjectFactory defaultFactory = new StateObjectFactoryImpl();
+
+ /**
+ * Creates an empty state. The returned state does not have an
+ * attached resolver.
+ *
+ * @return the created state
+ * @deprecated use {@link #createState(boolean) }
+ */
+ public State createState();
+
+ /**
+ * Creates an empty state with or without a resolver.
+ *
+ * @param resolver true if the created state should be initialized with a resolver.
+ * @return the created state
+ * @since 3.2
+ */
+ public State createState(boolean resolver);
+
+ /**
+ * Creates a new state that is a copy of the given state. The returned state
+ * will contain copies of all bundle descriptions in the given state.
+ * The user objects from the original bundle descriptions is not copied and
+ * no data pertaining to resolution is copied. The returned state will have a
+ * new resolver attached to it.
+ *
+ * @param state a state to be copied
+ * @return the created state
+ */
+ public State createState(State state);
+
+ /**
+ * Creates a bundle description from the given parameters.
+ *
+ * @param id id for the bundle
+ * @param symbolicName symbolic name for the bundle (may be
+ * <code>null</code>)
+ * @param version version for the bundle (may be <code>null</code>)
+ * @param location location for the bundle (may be <code>null</code>)
+ * @param required version constraints for all required bundles (may be
+ * <code>null</code>)
+ * @param host version constraint specifying the host for the bundle to be
+ * created. Should be <code>null</code> if the bundle is not a fragment
+ * @param imports version constraints for all packages imported
+ * (may be <code>null</code>)
+ * @param exports package descriptions of all the exported packages
+ * (may be <code>null</code>)
+ * @param providedPackages the list of provided packages (may be <code>null</code>)
+ * @param singleton whether the bundle created should be a singleton
+ * @return the created bundle description
+ * @deprecated use {@link #createBundleDescription(long, String, Version, String, BundleSpecification[], HostSpecification, ImportPackageSpecification[], ExportPackageDescription[], boolean, boolean, boolean, String, String[], GenericSpecification[], GenericDescription[])}
+ */
+ public BundleDescription createBundleDescription(long id, String symbolicName, Version version, String location, BundleSpecification[] required, HostSpecification host, ImportPackageSpecification[] imports, ExportPackageDescription[] exports, String[] providedPackages, boolean singleton);
+
+ /**
+ * Creates a bundle description from the given parameters.
+ *
+ * @param id id for the bundle
+ * @param symbolicName symbolic name for the bundle (may be
+ * <code>null</code>)
+ * @param version version for the bundle (may be <code>null</code>)
+ * @param location location for the bundle (may be <code>null</code>)
+ * @param required version constraints for all required bundles (may be
+ * <code>null</code>)
+ * @param host version constraint specifying the host for the bundle to be
+ * created. Should be <code>null</code> if the bundle is not a fragment
+ * @param imports version constraints for all packages imported
+ * (may be <code>null</code>)
+ * @param exports package descriptions of all the exported packages
+ * (may be <code>null</code>)
+ * @param providedPackages the list of provided packages (may be <code>null</code>)
+ * @param singleton whether the bundle created should be a singleton
+ * @param attachFragments whether the bundle allows fragments to attach
+ * @param dynamicFragments whether the bundle allows fragments to dynamically attach
+ * @param platformFilter the platform filter (may be <code>null</code>)
+ * @param executionEnvironment the execution environment (may be <code>null</code>)
+ * @param genericRequires the version constraints for all required capabilities (may be <code>null</code>)
+ * @param genericCapabilities the specifications of all the capabilities of the bundle (may be <code>null</code>)
+ * @return the created bundle description
+ * @deprecated use {@link #createBundleDescription(long, String, Version, String, BundleSpecification[], HostSpecification, ImportPackageSpecification[], ExportPackageDescription[], boolean, boolean, boolean, String, String[], GenericSpecification[], GenericDescription[])}
+ */
+ public BundleDescription createBundleDescription(long id, String symbolicName, Version version, String location, BundleSpecification[] required, HostSpecification host, ImportPackageSpecification[] imports, ExportPackageDescription[] exports, String[] providedPackages, boolean singleton, boolean attachFragments, boolean dynamicFragments, String platformFilter, String executionEnvironment, GenericSpecification[] genericRequires, GenericDescription[] genericCapabilities);
+
+ /**
+ * Creates a bundle description from the given parameters.
+ *
+ * @param id id for the bundle
+ * @param symbolicName symbolic name for the bundle (may be <code>null</code>)
+ * @param version version for the bundle (may be <code>null</code>)
+ * @param location location for the bundle (may be <code>null</code>)
+ * @param required version constraints for all required bundles (may be <code>null</code>)
+ * @param host version constraint specifying the host for the bundle to be created. Should be <code>null</code> if the bundle is not a fragment
+ * @param imports version constraints for all packages imported (may be <code>null</code>)
+ * @param exports package descriptions of all the exported packages (may be <code>null</code>)
+ * @param singleton whether the bundle created should be a singleton
+ * @param attachFragments whether the bundle allows fragments to attach
+ * @param dynamicFragments whether the bundle allows fragments to dynamically attach
+ * @param platformFilter the platform filter (may be <code>null</code>)
+ * @param executionEnvironments the execution environment (may be <code>null</code>)
+ * @param genericRequires the version constraints for all required capabilities (may be <code>null</code>)
+ * @param genericCapabilities the specifications of all the capabilities of the bundle (may be <code>null</code>)
+ * @return the created bundle description
+ */
+ public BundleDescription createBundleDescription(long id, String symbolicName, Version version, String location, BundleSpecification[] required, HostSpecification host, ImportPackageSpecification[] imports, ExportPackageDescription[] exports, boolean singleton, boolean attachFragments, boolean dynamicFragments, String platformFilter, String[] executionEnvironments, GenericSpecification[] genericRequires, GenericDescription[] genericCapabilities);
+
+ /**
+ * Creates a bundle description from the given parameters.
+ *
+ * @param id id for the bundle
+ * @param symbolicName symbolic name for the bundle (may be <code>null</code>)
+ * @param version version for the bundle (may be <code>null</code>)
+ * @param location location for the bundle (may be <code>null</code>)
+ * @param required version constraints for all required bundles (may be <code>null</code>)
+ * @param host version constraint specifying the host for the bundle to be created. Should be <code>null</code> if the bundle is not a fragment
+ * @param imports version constraints for all packages imported (may be <code>null</code>)
+ * @param exports package descriptions of all the exported packages (may be <code>null</code>)
+ * @param singleton whether the bundle created should be a singleton
+ * @param attachFragments whether the bundle allows fragments to attach
+ * @param dynamicFragments whether the bundle allows fragments to dynamically attach
+ * @param platformFilter the platform filter (may be <code>null</code>)
+ * @param executionEnvironments the execution environment (may be <code>null</code>)
+ * @param genericRequires the version constraints for all required capabilities (may be <code>null</code>)
+ * @param genericCapabilities the specifications of all the capabilities of the bundle (may be <code>null</code>)
+ * @param nativeCode the native code specification of the bundle (may be <code>null</code>)
+ * @return the created bundle description
+ * @since 3.4
+ */
+ public BundleDescription createBundleDescription(long id, String symbolicName, Version version, String location, BundleSpecification[] required, HostSpecification host, ImportPackageSpecification[] imports, ExportPackageDescription[] exports, boolean singleton, boolean attachFragments, boolean dynamicFragments, String platformFilter, String[] executionEnvironments, GenericSpecification[] genericRequires, GenericDescription[] genericCapabilities, NativeCodeSpecification nativeCode);
+
+ /**
+ * Creates a bundle description from the given parameters.
+ *
+ * @param id id for the bundle
+ * @param symbolicName the symbolic name of the bundle. This may include directives and/or attributes encoded using the Bundle-SymbolicName header.
+ * @param version version for the bundle (may be <code>null</code>)
+ * @param location location for the bundle (may be <code>null</code>)
+ * @param required version constraints for all required bundles (may be <code>null</code>)
+ * @param host version constraint specifying the host for the bundle to be created. Should be <code>null</code> if the bundle is not a fragment
+ * @param imports version constraints for all packages imported (may be <code>null</code>)
+ * @param exports package descriptions of all the exported packages (may be <code>null</code>)
+ * @param platformFilter the platform filter (may be <code>null</code>)
+ * @param executionEnvironments the execution environment (may be <code>null</code>)
+ * @param genericRequires the version constraints for all required capabilities (may be <code>null</code>)
+ * @param genericCapabilities the specifications of all the capabilities of the bundle (may be <code>null</code>)
+ * @param nativeCode the native code specification of the bundle (may be <code>null</code>)
+ * @return the created bundle description
+ * @since 3.8
+ */
+ public BundleDescription createBundleDescription(long id, String symbolicName, Version version, String location, BundleSpecification[] required, HostSpecification host, ImportPackageSpecification[] imports, ExportPackageDescription[] exports, String platformFilter, String[] executionEnvironments, GenericSpecification[] genericRequires, GenericDescription[] genericCapabilities, NativeCodeSpecification nativeCode);
+
+ /**
+ * Returns a bundle description based on the information in the supplied manifest dictionary.
+ * The manifest should contain String keys and String values which correspond to
+ * proper OSGi manifest headers and values.
+ *
+ * @param state the state for which the description is being created
+ * @param manifest a collection of OSGi manifest headers and values
+ * @param location the URL location of the bundle (may be <code>null</code>)
+ * @param id the id of the bundle
+ * @return a bundle description derived from the given information
+ * @throws BundleException if an error occurs while reading the manifest
+ */
+ public BundleDescription createBundleDescription(State state, Dictionary<String, String> manifest, String location, long id) throws BundleException;
+
+ /**
+ * Returns a bundle description based on the information in the supplied manifest dictionary.
+ * The manifest should contain String keys and String values which correspond to
+ * proper OSGi manifest headers and values.
+ *
+ * @param manifest a collection of OSGi manifest headers and values
+ * @param location the URL location of the bundle (may be <code>null</code>)
+ * @param id the id of the bundle
+ * @return a bundle description derived from the given information
+ * @throws BundleException if an error occurs while reading the manifest
+ * @deprecated use {@link #createBundleDescription(State, Dictionary, String, long)}
+ */
+ public BundleDescription createBundleDescription(Dictionary<String, String> manifest, String location, long id) throws BundleException;
+
+ /**
+ * Creates a bundle description that is a copy of the given description.
+ * The user object of the original bundle description is not copied.
+ *
+ * @param original the bundle description to be copied
+ * @return the created bundle description
+ */
+ public BundleDescription createBundleDescription(BundleDescription original);
+
+ /**
+ * Creates a bundle specification from the given parameters.
+ *
+ * @param requiredSymbolicName the symbolic name for the required bundle
+ * @param requiredVersionRange the required version range (may be <code>null</code>)
+ * @param export whether the required bundle should be re-exported
+ * @param optional whether the constraint should be optional
+ * @return the created bundle specification
+ * @see VersionConstraint for information on the available match rules
+ */
+ public BundleSpecification createBundleSpecification(String requiredSymbolicName, VersionRange requiredVersionRange, boolean export, boolean optional);
+
+ /**
+ * Creates a bundle specification that is a copy of the given constraint.
+ *
+ * @param original the constraint to be copied
+ * @return the created bundle specification
+ */
+ public BundleSpecification createBundleSpecification(BundleSpecification original);
+
+ /**
+ * Creates bundle specifications from the given declaration. The declaration uses
+ * the bundle manifest syntax for the Require-Bundle header.
+ * @param declaration a string declaring bundle specifications
+ * @return the bundle specifications
+ * @since 3.8
+ */
+ public List<BundleSpecification> createBundleSpecifications(String declaration);
+
+ /**
+ * Creates a host specification from the given parameters.
+ *
+ * @param hostSymbolicName the symbolic name for the host bundle
+ * @param hostVersionRange the version range for the host bundle (may be <code>null</code>)
+ * @return the created host specification
+ * @see VersionConstraint for information on the available match rules
+ */
+ public HostSpecification createHostSpecification(String hostSymbolicName, VersionRange hostVersionRange);
+
+ /**
+ * Creates host specifications from the given declaration. The declaration uses
+ * the bundle manifest syntax for the Fragment-Host header.
+ * @param declaration a string declaring host specifications
+ * @return the host specifications
+ * @since 3.8
+ */
+ public List<HostSpecification> createHostSpecifications(String declaration);
+
+ /**
+ * Creates a host specification that is a copy of the given constraint.
+ *
+ * @param original the constraint to be copied
+ * @return the created host specification
+ */
+ public HostSpecification createHostSpecification(HostSpecification original);
+
+ /**
+ * Creates an import package specification from the given parameters.
+ *
+ * @param packageName the package name
+ * @param versionRange the package versionRange (may be <code>null</code>).
+ * @param bundleSymbolicName the Bundle-SymbolicName of the bundle that must export the package (may be <code>null</code>)
+ * @param bundleVersionRange the bundle versionRange (may be <code>null</code>).
+ * @param directives the directives for this package (may be <code>null</code>)
+ * @param attributes the arbitrary attributes for the package import (may be <code>null</code>)
+ * @param importer the importing bundle (may be <code>null</code>)
+ * @return the created package specification
+ */
+ public ImportPackageSpecification createImportPackageSpecification(String packageName, VersionRange versionRange, String bundleSymbolicName, VersionRange bundleVersionRange, Map<String, ?> directives, Map<String, ?> attributes, BundleDescription importer);
+
+ /**
+ * Creates an import package specification that is a copy of the given import package
+ * @param original the import package to be copied
+ * @return the created package specification
+ */
+ public ImportPackageSpecification createImportPackageSpecification(ImportPackageSpecification original);
+
+ /**
+ * Creates an import package specifications from the given declaration. The declaration uses
+ * the bundle manifest syntax for the Import-Package header.
+ * @param declaration a string declaring import package specifications
+ * @return the import package specifications
+ * @since 3.8
+ */
+ public List<ImportPackageSpecification> createImportPackageSpecifications(String declaration);
+
+ /**
+ * Used by the Resolver to dynamically create ExportPackageDescription objects during the resolution process.
+ * The Resolver needs to create ExportPackageDescriptions dynamically for a host when a fragment.
+ * exports a package<p>
+ *
+ * @param packageName the package name
+ * @param version the version of the package (may be <code>null</code>)
+ * @param directives the directives for the package (may be <code>null</code>)
+ * @param attributes the attributes for the package (may be <code>null</code>)
+ * @param root whether the package is a root package
+ * @param exporter the exporter of the package (may be <code>null</code>)
+ * @return the created package
+ */
+ public ExportPackageDescription createExportPackageDescription(String packageName, Version version, Map<String, ?> directives, Map<String, ?> attributes, boolean root, BundleDescription exporter);
+
+ /**
+ * Creates a generic description from the given parameters
+ * @param name the name of the generic description
+ * @param type the type of the generic description (may be <code>null</code>)
+ * @param version the version of the generic description (may be <code>null</code>)
+ * @param attributes the attributes for the generic description (may be <code>null</code>)
+ * @return the created generic description
+ * @deprecated use {@link #createGenericDescription(String, String, Version, Map)}
+ */
+ public GenericDescription createGenericDescription(String name, String type, Version version, Map<String, ?> attributes);
+
+ /**
+ * Creates a generic description from the given parameters
+ * @param type the type of the generic description (may be <code>null</code>)
+ * @param attributes the attributes for the generic description (may be <code>null</code>)
+ * @param directives the directives for the generic description (may be <code>null</code>)
+ * @param supplier the supplier of the generic description (may be <code>null</code>)
+ * @return the created generic description
+ * @since 3.7
+ */
+ public GenericDescription createGenericDescription(String type, Map<String, ?> attributes, Map<String, String> directives, BundleDescription supplier);
+
+ /**
+ * Creates generic descriptions from the given declaration. The declaration uses
+ * the bundle manifest syntax for the Provide-Capability header.
+ * @param declaration a string declaring generic descriptions
+ * @return the generic descriptions
+ * @since 3.8
+ */
+ public List<GenericDescription> createGenericDescriptions(String declaration);
+
+ /**
+ * Creates a generic specification from the given parameters
+ * @param name the name of the generic specification
+ * @param type the type of the generic specification (may be <code>null</code>)
+ * @param matchingFilter the matching filter (may be <code>null</code>)
+ * @param optional whether the specification is optional
+ * @param multiple whether the specification allows for multiple suppliers
+ * @return the created generic specification
+ * @throws InvalidSyntaxException if the matching filter is invalid
+ */
+ public GenericSpecification createGenericSpecification(String name, String type, String matchingFilter, boolean optional, boolean multiple) throws InvalidSyntaxException;
+
+ /**
+ * Creates generic specifications from the given declaration. The declaration uses
+ * the bundle manifest syntax for the Require-Capability header.
+ * @param declaration a string declaring generic specifications
+ * @return the generic specifications
+ * @since 3.8
+ */
+ public List<GenericSpecification> createGenericSpecifications(String declaration);
+
+ /**
+ * Creates a native code specification from the given parameters
+ * @param nativeCodeDescriptions the native code descriptors
+ * @param optional whether the specification is optional
+ * @return the created native code specification
+ * @since 3.4
+ */
+ public NativeCodeSpecification createNativeCodeSpecification(NativeCodeDescription[] nativeCodeDescriptions, boolean optional);
+
+ /**
+ * Creates a native code description from the given parameters
+ * @param nativePaths the native code paths (may be <code>null</code>)
+ * @param processors the supported processors (may be <code>null</code>)
+ * @param osNames the supported operating system names (may be <code>null</code>)
+ * @param osVersions the supported operating system version ranges (may be <code>null</code>)
+ * @param languages the supported languages (may be <code>null</code>)
+ * @param filter the selection filter (may be <code>null</code>)
+ * @return the created native code description
+ * @throws InvalidSyntaxException if the selection filter is invalid
+ * @since 3.4
+ */
+ public NativeCodeDescription createNativeCodeDescription(String[] nativePaths, String[] processors, String[] osNames, VersionRange[] osVersions, String[] languages, String filter) throws InvalidSyntaxException;
+
+ /**
+ * Creates an export package specification that is a copy of the given constraint
+ * @param original the export package to be copied
+ * @return the created package
+ */
+ public ExportPackageDescription createExportPackageDescription(ExportPackageDescription original);
+
+ /**
+ * Creates export package descriptions from the given declaration. The declaration uses
+ * the bundle manifest syntax for the Export-Package header.
+ * @param declaration a string declaring export package descriptions
+ * @return the export package descriptions
+ * @since 3.8
+ */
+ public List<ExportPackageDescription> createExportPackageDescriptions(String declaration);
+
+ /**
+ * Persists the given state in the given output stream. Closes the stream.
+ *
+ * @param state the state to be written
+ * @param stream the stream where to write the state to
+ * @throws IOException if an IOException happens while writing the state to
+ * the stream
+ * @throws IllegalArgumentException if the state provided was not created by
+ * this factory
+ * @deprecated use {@link #writeState(State, File)} instead
+ * @since 3.1
+ */
+ public void writeState(State state, OutputStream stream) throws IOException;
+
+ /**
+ * Persists the given state in the given output stream. Closes the stream.
+ *
+ * @param state the state to be written
+ * @param stream the stream where to write the state to
+ * @throws IOException if an IOException happens while writing the state to
+ * the stream
+ * @throws IllegalArgumentException if the state provided was not created by
+ * this factory
+ * @deprecated use {@link #writeState(State, File)} instead
+ * @see #writeState(State, OutputStream)
+ */
+ public void writeState(State state, DataOutputStream stream) throws IOException;
+
+ /**
+ * Persists the given state in the given directory.
+ *
+ * @param state the state to be written
+ * @param stateDirectory the directory where to write the state to
+ * @throws IOException if an IOException happens while writing the state to
+ * the stream
+ * @throws IllegalArgumentException if the state provided was not created by
+ * this factory
+ */
+ public void writeState(State state, File stateDirectory) throws IOException;
+
+ /**
+ * Reads a persisted state from the given stream. Closes the stream.
+ *
+ * @param stream the stream where to read the state from
+ * @return the state read
+ * @throws IOException if an IOException happens while reading the state from
+ * the stream
+ * @deprecated use {@link #readState(File)} instead
+ * @since 3.1
+ */
+ public State readState(InputStream stream) throws IOException;
+
+ /**
+ * Reads a persisted state from the given stream. Closes the stream.
+ *
+ * @param stream the stream where to read the state from
+ * @return the state read
+ * @throws IOException if an IOException happens while reading the state from
+ * the stream
+ * @deprecated use {@link #readState(File)} instead
+ * @see #readState(InputStream)
+ */
+ public State readState(DataInputStream stream) throws IOException;
+
+ /**
+ * Reads a persisted state from the given directory.
+ *
+ * @param stateDirectory the directory where to read the state from
+ * @return the state read
+ * @throws IOException if an IOException happens while reading the state from
+ * the stream
+ */
+ public State readState(File stateDirectory) throws IOException;
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/StateWire.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/StateWire.java
new file mode 100644
index 000000000..2b2afdf4b
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/StateWire.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright (c) 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.service.resolver;
+
+/**
+ * A state wire represents a decision made by a resolver to wire a requirement to a capability.
+ * There are 4 parts to a state wire.
+ * <ul>
+ * <li>The requirement which may have been specified by a host bundle or one of its attached fragments.</li>
+ * <li>The host bundle which is associated with the requirement. There are cases where the host
+ * bundle may not be the same as the bundle which declared the requirement. For example, if a fragment
+ * specifies additional requirements.</li>
+ * <li>The capability which may have been specified by a host bundle or one of its attached fragments.</li>
+ * <li>The host bundle which is associated with the capability. There are cases where the host
+ * bundle may not be the same as the bundle which declared the capability. For example, if a fragment
+ * specifies additional capabilities.</li>
+ * </ul>
+ * @since 3.7
+ */
+public class StateWire {
+ private final BundleDescription requirementHost;
+ private final VersionConstraint declaredRequirement;
+ private final BundleDescription capabilityHost;
+ private final BaseDescription declaredCapability;
+
+ /**
+ * Constructs a new state wire.
+ * @param requirementHost the bundle hosting the requirement.
+ * @param declaredRequirement the declared requirement. The bundle declaring the requirement may be different from the requirement host.
+ * @param capabilityHost the bundle hosting the capability.
+ * @param declaredCapability the declared capability. The bundle declaring the capability may be different from the capability host.
+ */
+ public StateWire(BundleDescription requirementHost, VersionConstraint declaredRequirement, BundleDescription capabilityHost, BaseDescription declaredCapability) {
+ super();
+ this.requirementHost = requirementHost;
+ this.declaredRequirement = declaredRequirement;
+ this.capabilityHost = capabilityHost;
+ this.declaredCapability = declaredCapability;
+
+ }
+
+ /**
+ * Gets the requirement host.
+ * @return the requirement host.
+ */
+ public BundleDescription getRequirementHost() {
+ return requirementHost;
+ }
+
+ /**
+ * Gets the declared requirement.
+ * @return the declared requirement.
+ */
+ public VersionConstraint getDeclaredRequirement() {
+ return declaredRequirement;
+ }
+
+ /**
+ * gets the capability host.
+ * @return the capability host.
+ */
+ public BundleDescription getCapabilityHost() {
+ return capabilityHost;
+ }
+
+ /**
+ * gets the declared capability.
+ * @return the declared capability.
+ */
+ public BaseDescription getDeclaredCapability() {
+ return declaredCapability;
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/VersionConstraint.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/VersionConstraint.java
new file mode 100644
index 000000000..2f15515f8
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/VersionConstraint.java
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.service.resolver;
+
+import org.osgi.framework.wiring.BundleRequirement;
+
+/**
+ * VersionConstraints represent the relationship between two bundles (in the
+ * case of bundle requires) or a bundle and a package (in the case of import/export).
+ * <p>
+ * This interface is not intended to be implemented by clients. The
+ * {@link StateObjectFactory} should be used to construct instances.
+ * </p>
+ * @since 3.1
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface VersionConstraint extends Cloneable {
+
+ /**
+ * Returns this constraint's name.
+ *
+ * @return this constraint's name
+ */
+ public String getName();
+
+ /**
+ * Returns the version range for this constraint.
+ * @return the version range for this constraint, or <code>null</code>
+ */
+ public VersionRange getVersionRange();
+
+ /**
+ * Returns the bundle that declares this constraint.
+ *
+ * @return a bundle description
+ */
+ public BundleDescription getBundle();
+
+ /**
+ * Returns whether this constraint is resolved. A resolved constraint
+ * is guaranteed to have its supplier defined.
+ *
+ * @return <code>true</code> if this bundle is resolved, <code>false</code>
+ * otherwise
+ */
+ public boolean isResolved();
+
+ /**
+ * Returns whether this constraint could be satisfied by the given supplier.
+ * This will depend on the suppliers different attributes including its name,
+ * versions and other arbitrary attributes
+ *
+ * @param supplier a supplier to be tested against this constraint (may be
+ * <code>null</code>)
+ * @return <code>true</code> if this constraint could be resolved using the supplier,
+ * <code>false</code> otherwise
+ */
+ public boolean isSatisfiedBy(BaseDescription supplier);
+
+ /**
+ * Returns the supplier that satisfies this constraint, if it is resolved.
+ *
+ * @return a supplier, or <code>null</code>
+ * @see #isResolved()
+ */
+ public BaseDescription getSupplier();
+
+ /**
+ * Returns the requirement represented by this constraint.
+ * Some constraint types may not be able to represent
+ * a requirement. In such cases <code>null</code> is
+ * returned.
+ * @return the requirement represented by this constraint
+ * @since 3.7
+ */
+ public BundleRequirement getRequirement();
+
+ /**
+ * Returns the user object associated to this constraint, or
+ * <code>null</code> if none exists.
+ *
+ * @return the user object associated to this constraint,
+ * or <code>null</code>
+ * @since 3.8
+ */
+ public Object getUserObject();
+
+ /**
+ * Associates a user-provided object to this constraint, or
+ * removes an existing association, if <code>null</code> is provided. The
+ * provided object is not interpreted in any ways by this
+ * constrain.
+ *
+ * @param userObject an arbitrary object provided by the user, or
+ * <code>null</code>
+ * @since 3.8
+ */
+ public void setUserObject(Object userObject);
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/VersionRange.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/VersionRange.java
new file mode 100644
index 000000000..3b1c6506b
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/VersionRange.java
@@ -0,0 +1,127 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2012 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.service.resolver;
+
+import org.osgi.framework.Version;
+
+/**
+ * This class represents a version range.
+ * @since 3.1
+ * @noextend This class is not intended to be subclassed by clients.
+ */
+public class VersionRange extends org.osgi.framework.VersionRange {
+ private static final Version versionMax = new Version(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
+ private static final char INCLUDE_MIN = org.osgi.framework.VersionRange.LEFT_CLOSED;
+ private static final char EXCLUDE_MIN = org.osgi.framework.VersionRange.LEFT_OPEN;
+ private static final char INCLUDE_MAX = org.osgi.framework.VersionRange.RIGHT_CLOSED;
+ private static final char EXCLUDE_MAX = org.osgi.framework.VersionRange.RIGHT_OPEN;
+
+ /**
+ * An empty version range: "0.0.0". The empty version range includes all valid versions
+ * (any version greater than or equal to the version 0.0.0).
+ */
+ public static final VersionRange emptyRange = new VersionRange("0.0.0"); //$NON-NLS-1$
+
+ /**
+ * Constructs a VersionRange with the specified minVersion and maxVersion.
+ * @param minVersion the minimum version of the range. If <code>null</code>
+ * then {@link Version#emptyVersion} is used.
+ * @param maxVersion the maximum version of the range. If <code>null</code>
+ * then new Version(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE)
+ * is used.
+ */
+ public VersionRange(Version minVersion, boolean includeMin, Version maxVersion, boolean includeMax) {
+ super(includeMin ? INCLUDE_MIN : EXCLUDE_MIN, minVersion == null ? Version.emptyVersion : minVersion, versionMax.equals(maxVersion) ? null : maxVersion, includeMax ? INCLUDE_MAX : EXCLUDE_MAX);
+ }
+
+ /**
+ * Creates a version range from the specified string.
+ *
+ * <p>
+ * Here is the grammar for version range strings.
+ * <pre>
+ * version-range ::= interval | atleast
+ * interval ::= ( include-min | exclude-min ) min-version ',' max-version ( include-max | exclude-max )
+ * atleast ::= version
+ * floor ::= version
+ * ceiling ::= version
+ * include-min ::= '['
+ * exclude-min ::= '('
+ * include-max ::= ']'
+ * exclude-max ::= ')'
+ * </pre>
+ * </p>
+ *
+ * @param versionRange string representation of the version range or <code>null</code>
+ * for the empty range "0.0.0"
+ * @see Version#Version(String) definition of <code>version</code>
+ */
+ public VersionRange(String versionRange) {
+ super(versionRange == null || versionRange.length() == 0 ? "0.0.0" : versionRange); //$NON-NLS-1$
+ }
+
+ /**
+ * Returns the minimum Version of this VersionRange.
+ * @return the minimum Version of this VersionRange
+ */
+ public Version getMinimum() {
+ return getLeft();
+ }
+
+ /**
+ * Indicates if the minimum version is included in the version range.
+ * @return true if the minimum version is included in the version range;
+ * otherwise false is returned
+ */
+ public boolean getIncludeMinimum() {
+ return getLeftType() == VersionRange.LEFT_CLOSED;
+ }
+
+ /**
+ * Returns the maximum Version of this VersionRange.
+ * <p>
+ * This method is deprecated. For ranges that have no maximum this method
+ * incorrectly returns a version equal to
+ * <code>Version(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE)</code>.
+ * Use {@link org.osgi.framework.VersionRange#getRight()} instead.
+ * @return the maximum Version of this VersionRange
+ * @deprecated use {@link org.osgi.framework.VersionRange#getRight()}
+ */
+ public Version getMaximum() {
+ Version right = getRight();
+ return right == null ? versionMax : right;
+ }
+
+ /**
+ * Indicates if the maximum version is included in the version range.
+ * @return true if the maximum version is included in the version range;
+ * otherwise false is returned
+ */
+ public boolean getIncludeMaximum() {
+ return getRightType() == VersionRange.RIGHT_CLOSED;
+ }
+
+ /**
+ * Returns whether the given version is included in this VersionRange.
+ * This will depend on the minimum and maximum versions of this VersionRange
+ * and the given version.
+ *
+ * @param version a version to be tested for inclusion in this VersionRange.
+ * If <code>null</code> then {@link Version#emptyVersion} is used.
+ * @return <code>true</code> if the version is included,
+ * <code>false</code> otherwise
+ */
+ public boolean isIncluded(Version version) {
+ if (version == null)
+ version = Version.emptyVersion;
+ return includes(version);
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/extras/DescriptionReference.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/extras/DescriptionReference.java
new file mode 100644
index 000000000..d7d32cbf4
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/extras/DescriptionReference.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.service.resolver.extras;
+
+import org.eclipse.osgi.service.resolver.BaseDescription;
+
+/**
+ * A reference to a {@link BaseDescription}.
+ * @since 3.8
+ */
+public interface DescriptionReference {
+ /**
+ * Returns the {@code BaseDescription} object associated with this
+ * reference.
+ *
+ * @return The {@code BaseDescription} object associated with this
+ * reference.
+ */
+ public BaseDescription getDescription();
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/extras/Sortable.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/extras/Sortable.java
new file mode 100644
index 000000000..2442c5e2f
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/extras/Sortable.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.service.resolver.extras;
+
+import java.util.Comparator;
+
+/**
+ * Represents a collection of elements which can be sorted.
+ * @since 3.8
+ */
+public interface Sortable<E> {
+ /**
+ * Sorts collection of elements represented by this sortable according
+ * to the specified comparator.
+ * @param comparator the comparator used to sort the collection
+ * of elements represented by this sortable.
+ */
+ void sort(Comparator<E> comparator);
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/extras/SpecificationReference.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/extras/SpecificationReference.java
new file mode 100644
index 000000000..c63e2c181
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/extras/SpecificationReference.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2011 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.service.resolver.extras;
+
+import org.eclipse.osgi.service.resolver.VersionConstraint;
+
+/**
+ * A reference to a {@link VersionConstraint} specification.
+ * @since 3.8
+ */
+public interface SpecificationReference {
+ /**
+ * Returns the {@code VersionConstraint} object associated with this
+ * reference.
+ *
+ * @return The {@code VersionConstraint} object associated with this
+ * reference.
+ */
+ public VersionConstraint getSpecification();
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/package.html b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/package.html
new file mode 100644
index 000000000..04babd03f
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/resolver/package.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Package-level Javadoc</title>
+</head>
+<body>
+Provides the Equinox resolver API.
+<h2>
+Package Specification</h2>
+This package specifies the API for Equinox resolver.
+<p>
+Clients that need access to the Equinox runtime resolver will likely be interested
+in the types provided by this package.
+</p>
+</body>
+</html>
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/security/TrustEngine.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/security/TrustEngine.java
new file mode 100644
index 000000000..e4c9c977e
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/security/TrustEngine.java
@@ -0,0 +1,146 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.service.security;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.cert.Certificate;
+import org.eclipse.osgi.internal.signedcontent.TrustEngineListener;
+
+/**
+ * A <code>TrustEngine</code> is used to establish the authenticity of a
+ * {@link Certificate} chain.
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ * @since 3.4
+ */
+public abstract class TrustEngine {
+ /**
+ * Returns the certificate trust anchor contained in the specified chain which
+ * was used to establish the authenticity of the chain. If no
+ * trust anchor is found in the chain then <code>null</code> is returned.
+ * @param chain - a complete or incomplete certificate chain, implementations *MAY* complete chains
+ * @return - the certificate trust anchor used to establish authenticity
+ * @throws IOException if there is a problem connecting to the backing store
+ */
+ public abstract Certificate findTrustAnchor(Certificate[] chain) throws IOException;
+
+ /**
+ * Add a trust anchor point to this trust engine. A trust anchor implies that a certificate,
+ * and any of its children, is to be considered trusted. If <code>null</code> is used
+ * as the alias then an alias will be generated based on the trust anchor certificate.
+ * @param anchor - the certificate to add as an anchor point
+ * @param alias - a unique and human-readable 'friendly name' which can be used to reference the certificate.
+ * A <code>null</code> value may be used.
+ * @return the alias used to store the entry
+ * @throws IOException if there is a problem connecting to the backing store
+ * @throws GeneralSecurityException if there is a certificate problem
+ * @throws IllegalArgumentException if the alias or anchor already exist in this trust engine
+ */
+ public String addTrustAnchor(Certificate anchor, String alias) throws IOException, GeneralSecurityException {
+ String storedAlias = doAddTrustAnchor(anchor, alias);
+ TrustEngineListener listener = TrustEngineListener.getInstance();
+ if (listener != null)
+ listener.addedTrustAnchor(anchor);
+ return storedAlias;
+ }
+
+ /**
+ * Add a trust anchor point to this trust engine. A trust anchor implies that a certificate,
+ * and any of its children, is to be considered trusted. If <code>null</code> is used
+ * as the alias then an alias will be generated based on the trust anchor certificate.
+ * @param anchor - the certificate to add as an anchor point
+ * @param alias - a unique and human-readable 'friendly name' which can be used to reference the certificate.
+ * A <code>null</code> value may be used.
+ * @return the alias used to store the entry
+ * @throws IOException if there is a problem connecting to the backing store
+ * @throws GeneralSecurityException if there is a certificate problem
+ * @throws IllegalArgumentException if the alias or anchor already exist in this trust engine
+ */
+ protected abstract String doAddTrustAnchor(Certificate anchor, String alias) throws IOException, GeneralSecurityException;
+
+ /**
+ * Remove a trust anchor point from the engine, based on the certificate itself.
+ * @param anchor - the certificate to be removed
+ * @throws IOException if there is a problem connecting to the backing store
+ * @throws GeneralSecurityException if there is a certificate problem
+ */
+ public final void removeTrustAnchor(Certificate anchor) throws IOException, GeneralSecurityException {
+ doRemoveTrustAnchor(anchor);
+ TrustEngineListener listener = TrustEngineListener.getInstance();
+ if (listener != null)
+ listener.removedTrustAnchor(anchor);
+ }
+
+ /**
+ * Remove a trust anchor point from the engine, based on the certificate itself.
+ * @param anchor - the certificate to be removed
+ * @throws IOException if there is a problem connecting to the backing store
+ * @throws GeneralSecurityException if there is a certificate problem
+ */
+ protected abstract void doRemoveTrustAnchor(Certificate anchor) throws IOException, GeneralSecurityException;
+
+ /**
+ * Remove a trust anchor point from the engine, based on the human readable "friendly name"
+ * @param alias - the name of the trust anchor
+ * @throws IOException if there is a problem connecting to the backing store
+ * @throws GeneralSecurityException if there is a certificate problem
+ */
+ public void removeTrustAnchor(String alias) throws IOException, GeneralSecurityException {
+ Certificate existing = getTrustAnchor(alias);
+ doRemoveTrustAnchor(alias);
+ if (existing != null) {
+ TrustEngineListener listener = TrustEngineListener.getInstance();
+ if (listener != null)
+ listener.removedTrustAnchor(existing);
+ }
+ }
+
+ /**
+ * Remove a trust anchor point from the engine, based on the human readable "friendly name"
+ * @param alias - the name of the trust anchor
+ * @throws IOException if there is a problem connecting to the backing store
+ * @throws GeneralSecurityException if there is a certificate problem
+ */
+ protected abstract void doRemoveTrustAnchor(String alias) throws IOException, GeneralSecurityException;
+
+ /**
+ * Return the certificate associated with the unique "friendly name" in the engine.
+ * @param alias - the friendly name
+ * @return the associated trust anchor
+ * @throws IOException if there is a problem connecting to the backing store
+ * @throws GeneralSecurityException if there is a certificate problem
+ */
+ public abstract Certificate getTrustAnchor(String alias) throws IOException, GeneralSecurityException;
+
+ /**
+ * Return the list of friendly name aliases for the TrustAnchors installed in the engine.
+ * @return string[] - the list of friendly name aliases
+ * @throws IOException if there is a problem connecting to the backing store
+ * @throws GeneralSecurityException if there is a certificate problem
+ */
+ public abstract String[] getAliases() throws IOException, GeneralSecurityException;
+
+ /**
+ * Return a value indicate whether this trust engine is read-only.
+ *
+ * @return true if this trust engine is read-only false otherwise.
+ */
+ public abstract boolean isReadOnly();
+
+ /**
+ * Return a representation string of this trust engine
+ *
+ * @return a string
+ */
+ public abstract String getName();
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/security/package.html b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/security/package.html
new file mode 100644
index 000000000..de7f5c952
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/service/security/package.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Package-level Javadoc</title>
+</head>
+<body>
+Equinox security services
+<h2>
+Package Specification</h2>
+<p>This package provides service APIs related to security.
+</p>
+</body>
+</html>
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/signedcontent/InvalidContentException.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/signedcontent/InvalidContentException.java
new file mode 100644
index 000000000..846c307df
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/signedcontent/InvalidContentException.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.signedcontent;
+
+import java.io.IOException;
+
+/**
+ * Indicates that signed content is invalid according to one of the signers.
+ * @since 3.4
+ * @noextend This class is not intended to be subclassed by clients.
+ */
+public class InvalidContentException extends IOException {
+ private static final long serialVersionUID = -399150159330289387L;
+ // TODO may want to add error codes to indicate the reason for the invalid/corruption error.
+ private final Throwable cause;
+
+ /**
+ * Constructs an <code>InvalidContentException</code> with the specified detail
+ * message and cause.
+ *
+ * @param message the exception message
+ * @param cause the cause, may be <code>null</code>
+ */
+ public InvalidContentException(String message, Throwable cause) {
+ super(message);
+ this.cause = cause;
+ }
+
+ /**
+ * Returns the cause of this exception or <code>null</code> if no cause
+ * was specified when this exception was created.
+ *
+ * @return The cause of this exception or <code>null</code> if no cause was created.
+ */
+ public Throwable getCause() {
+ return cause;
+ }
+
+ /**
+ * The cause of this exception can only be set when constructed.
+ *
+ * @param t Cause of the exception.
+ * @return This object.
+ * @throws java.lang.IllegalStateException This method will always throw an
+ * <code>IllegalStateException</code> since the cause of this
+ * exception can only be set when constructed.
+ */
+ public Throwable initCause(Throwable t) {
+ throw new IllegalStateException();
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/signedcontent/SignedContent.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/signedcontent/SignedContent.java
new file mode 100644
index 000000000..918f1a03c
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/signedcontent/SignedContent.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.signedcontent;
+
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.util.Date;
+
+/**
+ * A <code>SignedContent</code> object represents content which may be signed. A
+ * {@link SignedContentFactory} is used to create signed content objects.
+ * <p>
+ * A <code>SignedContent</code> object is intended to provide information about
+ * the signers of the content, and cannot be used to access the actual data of the content.
+ * </p>
+ * <p>
+ * This interface is not intended to be implemented by clients.
+ * </p>
+ * @since 3.4
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface SignedContent {
+
+ /**
+ * Returns all entries of the content. The returned entries can be used
+ * to verify the entry content using {@link SignedContentEntry#verify()} and
+ * get signer info for each entry in this content using {@link SignedContentEntry#getSignerInfos()}.
+ * Note that this operation may be expensive because it requires an
+ * exhaustive search for entries over the entire content.
+ * <p>
+ * Unsigned entries are included in the result. Entries for which signer info exists
+ * but no content is found are also returned. For example, when an entry is removed from
+ * a signed jar but the jar is not resigned, the signer thinks the entry should exist
+ * but the content got removed. This would be considered an invalid entry which would fail verification.
+ * </p>
+ * @return all entries of the content
+ */
+ public SignedContentEntry[] getSignedEntries();
+
+ /**
+ * Returns the signed entry for the specified name.
+ * @param name the name of the entry
+ * @return the entry or null if the entry could not be found
+ */
+ public SignedContentEntry getSignedEntry(String name);
+
+ /**
+ * Returns all the signer infos for this <code>SignedContent</code>. If the content
+ * is not signed then an empty array is returned.
+ * @return all the signer infos for this <code>SignedContent</code>
+ */
+ public SignerInfo[] getSignerInfos();
+
+ /**
+ * Returns true if the content is signed; false otherwise. This is a convenience method
+ * equivalent to calling <code>{@link #getSignerInfos()}.length > 0</code>
+ * @return true if the content is signed
+ */
+ public boolean isSigned();
+
+ /**
+ * Returns the signing time for the signer info. If no TSA signers exist then null is returned
+ * @param signerInfo the signer info to get the signing time for
+ * @return the signing time
+ */
+ public Date getSigningTime(SignerInfo signerInfo);
+
+ /**
+ * Returns the TSA signer info used to authenticate the signer time of a signer info.
+ * @param signerInfo the signer info to get the TSA signer for
+ * @return the TSA signer info
+ */
+ public SignerInfo getTSASignerInfo(SignerInfo signerInfo);
+
+ /**
+ * Checks if the certificates are valid for the specified signer. If the signer has a singing time
+ * returned by {@link #getSigningTime(SignerInfo)} then that time is used to check the
+ * validity of the certificates; otherwise the current time is used.
+ * @param signerInfo the signer info to check validity for.
+ * @throws CertificateExpiredException if one of the certificates of this signer is expired
+ * @throws CertificateNotYetValidException if one of the certificates of this signer is not yet valid
+ */
+ public void checkValidity(SignerInfo signerInfo) throws CertificateExpiredException, CertificateNotYetValidException;
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/signedcontent/SignedContentEntry.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/signedcontent/SignedContentEntry.java
new file mode 100644
index 000000000..27f154b48
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/signedcontent/SignedContentEntry.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.signedcontent;
+
+import java.io.IOException;
+
+// encapsulates the status of an entry: isSigned, timestamp info, SignerInfos, etc
+// implemented by SignedBundleFile.SignedBundleEntry
+/**
+ * A <code>SignedContentEntry</code> represents a content entry which may be
+ * signed.
+ * <p>
+ * A <code>SignedContentEntry</code> object is intended to provide information about
+ * the signers of the content entry, and cannot be used to access the actual data of the entry.
+ * </p>
+ * <p>
+ * This interface is not intended to be implemented by clients.
+ * </p>
+ * @since 3.4
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface SignedContentEntry {
+ /**
+ * Returns the name of the entry.
+ * @return the name of the entry.
+ */
+ public String getName();
+
+ /**
+ * Returns the signer infos for this <code>SignedContentEntry</code>. If the entry
+ * is not signed then an empty array is returned.
+ * @return the signer infos for this <code>SignedContentEntry</code>
+ */
+ public SignerInfo[] getSignerInfos();
+
+ /**
+ * Returns true if the entry is signed; false otherwise. This is a convenience method
+ * equivalent to calling <code>{@link #getSignerInfos()}.length > 0</code>
+ * @return true if the content is signed
+ */
+ public boolean isSigned();
+
+ // Does the digest of this entry match what is expected?
+ // TODO: what does this mean in the face of multiple signers
+ /**
+ * Verifies the content of this this entry is valid.
+ * @throws IOException if an error occurred reading the entry content
+ * @throws InvalidContentException if the entry content is not valid
+ */
+ public void verify() throws IOException, InvalidContentException;
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/signedcontent/SignedContentFactory.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/signedcontent/SignedContentFactory.java
new file mode 100644
index 000000000..bfd405dac
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/signedcontent/SignedContentFactory.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.signedcontent;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.*;
+import java.security.cert.CertificateException;
+import org.osgi.framework.Bundle;
+
+/**
+ * A factory used to create {@link SignedContent} objects.
+ * <p>
+ * The framework will register a factory implementation as an OSGi service.
+ * This service can be used to get <code>SignedContent</code> for a bundle.
+ * It can also be used to get <code>SignedContent</code> for a repository file.
+ * The supported formats for file repositories are jar files and directories containing the
+ * content of an extracted jar.
+ * </p>
+ * <p>
+ * This interface is not intended to be implemented by clients.
+ * </p>
+ * @since 3.4
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface SignedContentFactory {
+ /**
+ * Returns a <code>SignedContent</code> object for the specified content of a repository.
+ * @param content the content of the repository
+ * @return signed content for the specified repository
+ * @throws IOException if an IO exception occurs while reading the repository
+ * @throws NoSuchProviderException if there's no security provider for the signed content
+ * @throws NoSuchAlgorithmException if the cryptographic algorithm is not available for the signed content
+ * @throws CertificateException if there is a problem with one of the certificates of the signed content
+ * @throws SignatureException if there is a problem with one of the signatures of the signed content
+ * @throws InvalidKeyException if there is a problem with one of the certificate keys of the signed content
+ */
+ public SignedContent getSignedContent(File content) throws IOException, InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException;
+
+ /**
+ * Returns a <code>SignedContent</code> object for the specified bundle.
+ * @param bundle the bundle to get a signed content for.
+ * @return signed content for the specified bundle.
+ * @throws IOException if an IO exception occurs while reading the bundle content
+ * @throws NoSuchProviderException if there's no security provider for the signed content
+ * @throws NoSuchAlgorithmException if the cryptographic algorithm is not available for the signed content
+ * @throws CertificateException if there is a problem with one of the certificates of the signed content
+ * @throws SignatureException if there is a problem with one of the signatures of the signed content
+ * @throws InvalidKeyException if there is a problem with one of the certificate keys of the signed content
+ */
+ public SignedContent getSignedContent(Bundle bundle) throws IOException, InvalidKeyException, SignatureException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException;
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/signedcontent/SignerInfo.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/signedcontent/SignerInfo.java
new file mode 100644
index 000000000..c5bc14dd8
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/signedcontent/SignerInfo.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.signedcontent;
+
+import java.security.cert.Certificate;
+
+/**
+ * A <code>SignerInfo</code> object represents a single signer chain.
+ * <p>
+ * This interface is not intended to be implemented by clients.
+ * </p>
+ * @since 3.4
+ * @noimplement This interface is not intended to be implemented by clients.
+ */
+public interface SignerInfo {
+
+ /**
+ * Returns the certificate chain
+ * @return the certificate chain
+ */
+ public Certificate[] getCertificateChain();
+
+ /**
+ * Returns the certificate trust anchor used to establish authenticity.
+ * If authenticity cannot be established then <code>null</code> is returned.
+ * @return the trust anchor
+ */
+ public Certificate getTrustAnchor();
+
+ /**
+ * Returns true if the trust anchor has been authenticated. This is a convenience
+ * method equivalent to calling <code>{@link #getTrustAnchor()} != null</code>
+ * @return true if the the signer info is trusted
+ */
+ public boolean isTrusted();
+
+ /**
+ * Returns the <code>MessageDigest</code> algorithm used to verify content signed by this
+ * signer info.
+ * @return the algorithm
+ */
+ public String getMessageDigestAlgorithm();
+
+ // TODO need more thought here, TrustEngines could get stale since they are services, leaving off for now unless until we understand the usecase for this.
+ //public TrustEngine getTrustEngine();
+
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/signedcontent/package.html b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/signedcontent/package.html
new file mode 100644
index 000000000..79563e3b7
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/signedcontent/package.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Package-level Javadoc</title>
+</head>
+<body>
+Equinox Signed Content
+<h2>
+Package Specification</h2>
+<p>This package provides API for accessing signer information from signed content.
+</p>
+</body>
+</html>
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/util/TextProcessor.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/util/TextProcessor.java
new file mode 100644
index 000000000..6f2b039ae
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/util/TextProcessor.java
@@ -0,0 +1,298 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.osgi.util;
+
+import java.util.Locale;
+
+/**
+ * This class is used to process strings that have special semantic meaning
+ * (such as file paths) in RTL-oriented locales so that they render in a way
+ * that does not corrupt the semantic meaning of the string but also maintains
+ * compliance with the Unicode BiDi algorithm of rendering Bidirectional text.
+ * <p>
+ * Processing of the string is done by breaking it down into segments that are
+ * specified by a set of user provided delimiters. Directional punctuation
+ * characters are injected into the string in order to ensure the string retains
+ * its semantic meaning and conforms with the Unicode BiDi algorithm within each
+ * segment.
+ * </p>
+ *
+ * @since 3.2
+ * @noextend This class is not intended to be subclassed by clients.
+ */
+public class TextProcessor {
+
+ // commonly used delimiters
+ /**
+ * Dot (.) delimiter. Used most often in package names and file extensions.
+ */
+ private static final String DOT = "."; //$NON-NLS-1$
+
+ /**
+ * Colon (:) delimiter. Used most often in file paths and URLs.
+ */
+ private static final String COLON = ":"; //$NON-NLS-1$
+
+ /**
+ * Forward slash (/) delimiter. Used most often in file paths and URLs.
+ */
+ private static final String FILE_SEP_FSLASH = "/"; //$NON-NLS-1$
+
+ /**
+ * Backslash (\) delimiter. Used most often in file paths.
+ */
+ private static final String FILE_SEP_BSLASH = "\\"; //$NON-NLS-1$
+
+ /**
+ * The default set of delimiters to use to segment a string.
+ */
+ private static final String delimiterString = DOT + COLON + FILE_SEP_FSLASH + FILE_SEP_BSLASH;
+
+ // left to right marker
+ private static final char LRM = '\u200e';
+
+ // left to right embedding
+ private static final char LRE = '\u202a';
+
+ // pop directional format
+ private static final char PDF = '\u202c';
+
+ // whether or not processing is needed
+ private static boolean IS_PROCESSING_NEEDED = false;
+
+ // constant used to indicate an LRM need not precede a delimiter
+ private static final int INDEX_NOT_SET = 999999999;
+
+ static {
+ Locale locale = Locale.getDefault();
+ String lang = locale.getLanguage();
+
+ if ("iw".equals(lang) || "he".equals(lang) || "ar".equals(lang) || "fa".equals(lang) || "ur".equals(lang)) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
+ String osName = System.getProperty("os.name").toLowerCase(); //$NON-NLS-1$
+ if (osName.startsWith("windows") || osName.startsWith("linux") || osName.startsWith("mac")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ IS_PROCESSING_NEEDED = true;
+ }
+ }
+ }
+
+ /**
+ * Process the given text and return a string with the appropriate
+ * substitution based on the locale. This is equivalent to calling
+ * <code>process(String, String)</code> with the default set of
+ * delimiters.
+ *
+ * @param text
+ * the text to be processed
+ * @return the manipulated string
+ * @see #process(String, String)
+ * @see #getDefaultDelimiters()
+ */
+ public static String process(String text) {
+ if (!IS_PROCESSING_NEEDED || text == null || text.length() <= 1)
+ return text;
+ return process(text, getDefaultDelimiters());
+ }
+
+ /**
+ * Process a string that has a particular semantic meaning to render on BiDi
+ * locales in way that maintains the semantic meaning of the text, but
+ * differs from the Unicode BiDi algorithm. The text is segmented according
+ * to the provided delimiters. Each segment has the Unicode BiDi algorithm
+ * applied to it, but as a whole, the string is oriented left to right.
+ * <p>
+ * For example a file path such as <tt>d:\myFolder\FOLDER\MYFILE.java</tt>
+ * (where capital letters indicate RTL text) should render as
+ * <tt>d:\myFolder\REDLOF\ELIFYM.java</tt> when using the Unicode BiDi
+ * algorithm and segmenting the string according to the specified delimiter
+ * set.
+ * </p>
+ * <p>
+ * The following algorithm is used:
+ * <ol>
+ * <li>Scan the string to locate the delimiters.</li>
+ * <li>While scanning, note the direction of the last strong character
+ * scanned. Strong characters are characters which have a BiDi
+ * classification of L, R or AL as defined in the Unicode standard.</li>
+ * <li>If the last strong character before a separator is of class R or AL,
+ * add a LRM before the separator. Since LRM itself is a strong L character,
+ * following separators do not need an LRM until a strong R or AL character
+ * is found.</li>
+ * <li>If the component where the pattern is displayed has a RTL basic
+ * direction, add a LRE at the beginning of the pattern and a PDF at its
+ * end. The string is considered to have RTL direction if it contains RTL
+ * characters and the runtime locale is BiDi. There is no need to add
+ * LRE/PDF if the string begins with an LTR letter, contains no RTL letter,
+ * and ends with either a LTR letter or a digit.</li>
+ * </ol>
+ * </p>
+ * <p>
+ * NOTE: this method will change the shape of the original string passed in
+ * by inserting punctuation characters into the text in order to make it
+ * render to correctly reflect the semantic meaning of the text. Methods
+ * like <code>String.equals(String)</code> and
+ * <code>String.length()</code> called on the resulting string will not
+ * return the same values as would be returned for the original string.
+ * </p>
+ *
+ * @param str
+ * the text to process, if <code>null</code> return the string
+ * as it was passed in
+ * @param delimiter
+ * delimiters by which the string will be segmented, if
+ * <code>null</code> the default delimiters are used
+ * @return the processed string
+ */
+ public static String process(String str, String delimiter) {
+ if (!IS_PROCESSING_NEEDED || str == null || str.length() <= 1)
+ return str;
+
+ // do not process a string that has already been processed.
+ if (str.charAt(0) == LRE && str.charAt(str.length() - 1) == PDF) {
+ return str;
+ }
+
+ // String contains RTL characters
+ boolean isStringBidi = false;
+ // Last strong character is RTL
+ boolean isLastRTL = false;
+ // Last candidate delimiter index
+ int delimIndex = INDEX_NOT_SET;
+
+ delimiter = delimiter == null ? getDefaultDelimiters() : delimiter;
+
+ StringBuffer target = new StringBuffer();
+ target.append(LRE);
+ char ch;
+
+ for (int i = 0, n = str.length(); i < n; i++) {
+ ch = str.charAt(i);
+ if (delimiter.indexOf(ch) != -1) {
+ // character is a delimiter, note its index in the buffer
+ if (isLastRTL) {
+ delimIndex = target.length();
+ }
+ } else if (Character.isDigit(ch)) {
+ if (delimIndex != INDEX_NOT_SET) {
+ // consecutive neutral and weak directional characters
+ // explicitly force direction to be LRM
+ target.insert(delimIndex, LRM);
+ delimIndex = INDEX_NOT_SET;
+ isLastRTL = false;
+ }
+ } else if (Character.isLetter(ch)) {
+ if (isRTL(ch)) {
+ isStringBidi = true;
+ if (delimIndex != INDEX_NOT_SET) {
+ // neutral character followed by strong right directional character
+ // explicitly force direction to be LRM
+ target.insert(delimIndex, LRM);
+ delimIndex = INDEX_NOT_SET;
+ }
+ isLastRTL = true;
+ } else {
+ // strong LTR character, no LRM will be required
+ delimIndex = INDEX_NOT_SET;
+ isLastRTL = false;
+ }
+ }
+ target.append(ch);
+ }
+ /*
+ * TextProcessor is not aware of the orientation of the component owning
+ * the processed string. Enclose the string in LRE/PDF in either of 2
+ * cases:
+ * (1) The string contains BiDi characters - implying that the
+ * string appearance depends on the basic orientation
+ * (2) The runtime locale is BiDi AND either the string does not start with
+ * an LTR character or it ends with LTR char or digit.
+ */
+ if (isStringBidi || !Character.isLetter(str.charAt(0)) || isNeutral(str.charAt(str.length() - 1))) {
+ target.append(PDF);
+ return target.toString();
+ }
+ // Otherwise, return the original string
+ return str;
+ }
+
+ /**
+ * Removes directional marker characters in the given string that were inserted by
+ * utilizing the <code>process(String)</code> or <code>process(String, String)</code>
+ * methods.
+ *
+ * @param str string with directional markers to remove
+ * @return string with no directional markers
+ * @see #process(String)
+ * @see #process(String, String)
+ * @since 3.3
+ */
+ public static String deprocess(String str) {
+ if (!IS_PROCESSING_NEEDED || str == null || str.length() <= 1)
+ return str;
+
+ StringBuffer buf = new StringBuffer();
+ for (int i = 0; i < str.length(); i++) {
+ char c = str.charAt(i);
+ switch (c) {
+ case LRE :
+ continue;
+ case PDF :
+ continue;
+ case LRM :
+ continue;
+ default :
+ buf.append(c);
+ }
+ }
+
+ return buf.toString();
+ }
+
+ /**
+ * Return the string containing all the default delimiter characters to be
+ * used to segment a given string.
+ *
+ * @return delimiter string
+ */
+ public static String getDefaultDelimiters() {
+ return delimiterString;
+ }
+
+ /*
+ * Return whether or not the character falls is right to left oriented.
+ */
+ private static boolean isRTL(char c) {
+ /*
+ * Cannot use Character.getDirectionality() since the OSGi library can
+ * be compiled with execution environments that pre-date that API.
+ *
+ * The first range of characters is Unicode Hebrew and Arabic
+ * characters. The second range of characters is Unicode Hebrew and
+ * Arabic presentation forms.
+ *
+ * NOTE: Farsi and Urdu fall within the Arabic scripts.
+ */
+ return (((c >= 0x05d0) && (c <= 0x07b1)) || ((c >= 0xfb1d) && (c <= 0xfefc)));
+ }
+
+ /*
+ * Return whether or not the given character has a weak directional type
+ */
+ private static boolean isNeutral(char c) {
+ return !(Character.isDigit(c) || Character.isLetter(c));
+ }
+
+ /*
+ * Constructor for the class.
+ */
+ private TextProcessor() {
+ // prevent instantiation
+ }
+}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/util/package.html b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/util/package.html
new file mode 100644
index 000000000..08e65eda1
--- /dev/null
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/util/package.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Package-level Javadoc</title>
+</head>
+<body>
+Provides utility classes for NLS support and bundle manifest header parsing.
+<h2>
+Package Specification</h2>
+This package specifies the API related to NLS support and bundle manifest header parsing.
+<p>
+Clients with translatable text or parsing bundle manifest headers will likely be interested in the types
+provided by this package.
+</p>
+</body>
+</html>

Back to the top